Compare commits
No commits in common. 'master' and 'kernel-mode' have entirely different histories.
master
...
kernel-mod
@ -1,2 +0,0 @@
|
|||||||
# introduces black formatting
|
|
||||||
5515c7795cfd690d346aad10ce17b30acf914648
|
|
@ -1,49 +0,0 @@
|
|||||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
|
||||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
|
||||||
|
|
||||||
name: CI - Python-based Testing
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Trigger the workflow on push or pull request,
|
|
||||||
# but only for the master branch
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ['3.8','3.10']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Upgrade pip
|
|
||||||
run: |
|
|
||||||
pip install --upgrade pip
|
|
||||||
|
|
||||||
- name: Install the package locally
|
|
||||||
run: pip install -r requirements.txt -r requirements-dev.txt
|
|
||||||
|
|
||||||
- name: Test with pytest
|
|
||||||
run: |
|
|
||||||
pytest -W error
|
|
||||||
- name: Test with lit
|
|
||||||
run: |
|
|
||||||
lit -v test/filecheck
|
|
||||||
|
|
||||||
#- name: Execute lit tests
|
|
||||||
# run: |
|
|
||||||
# export PYTHONPATH=$(pwd)
|
|
||||||
# lit -v tests/filecheck/
|
|
@ -1,34 +0,0 @@
|
|||||||
# This workflow check the format all files in the repository
|
|
||||||
# * It checks that all nonempty files have a newline at the end
|
|
||||||
# * It checks that there are no whitespaces at the end of lines
|
|
||||||
# * It checks that Python files are formatted with black
|
|
||||||
|
|
||||||
name: Code Formatting
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
code-formatting:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ['3.10']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Upgrade pip
|
|
||||||
run: |
|
|
||||||
pip install --upgrade pip
|
|
||||||
|
|
||||||
- name: Run code formatting checks with pre-commit
|
|
||||||
uses: pre-commit/action@v3.0.0
|
|
@ -1,98 +0,0 @@
|
|||||||
name: Deploy JupiterLite Page
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Trigger the workflow on push or pull request,
|
|
||||||
# but only for the master branch
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout RiscEmu
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
path: riscemu
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install jupyterlite[all] libarchive-c build pyodide-build
|
|
||||||
|
|
||||||
- name: Build RiscEmu source distribution
|
|
||||||
run: |
|
|
||||||
cd riscemu
|
|
||||||
python setup.py sdist
|
|
||||||
|
|
||||||
# Pyodide is cached, so cloned only if not present in the cache, otherwise
|
|
||||||
# just checked out to whatever desired version and partially rebuilt.
|
|
||||||
|
|
||||||
- name: Restore cached Pyodide tree
|
|
||||||
id: cache-pyodide
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: pyodide
|
|
||||||
key: pyodide
|
|
||||||
|
|
||||||
- name: Clone pyodide if not cached
|
|
||||||
if: steps.cache-pyodide.outputs.cache-hit != 'true'
|
|
||||||
run: git clone https://github.com/pyodide/pyodide.git
|
|
||||||
|
|
||||||
# Clean the xDSL and FrozenList package folders, generate their skeletons
|
|
||||||
# and do the necessary updates before building.
|
|
||||||
- name: Build custom Pyodide distribution
|
|
||||||
run: |
|
|
||||||
|
|
||||||
cd pyodide
|
|
||||||
git fetch --all
|
|
||||||
git checkout 0.22.0a3
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
sudo apt update && sudo apt install f2c
|
|
||||||
|
|
||||||
rm -rf packages/riscemu
|
|
||||||
pyodide skeleton pypi riscemu
|
|
||||||
|
|
||||||
PYODIDE_PACKAGES="riscemu" make
|
|
||||||
|
|
||||||
- name: Build the JupyterLite site
|
|
||||||
run: |
|
|
||||||
mkdir content
|
|
||||||
cp riscemu/docs/* content -r
|
|
||||||
cp riscemu/examples content -r
|
|
||||||
|
|
||||||
rm -rf pyodide/pyodide
|
|
||||||
mkdir pyodide/pyodide
|
|
||||||
mv pyodide/dist pyodide/pyodide/pyodide
|
|
||||||
|
|
||||||
python -m jupyter lite build --contents content --pyodide pyodide/pyodide
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v1
|
|
||||||
with:
|
|
||||||
path: ./_output
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: build
|
|
||||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v1
|
|
@ -1,22 +0,0 @@
|
|||||||
name: Upload to PyPI
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: publishing
|
|
||||||
permissions:
|
|
||||||
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
# install build package and build dist
|
|
||||||
- name: Build distribution
|
|
||||||
run: >-
|
|
||||||
python3 -m pip install build --user &&
|
|
||||||
python3 -m build --sdist --wheel --outdir dist/
|
|
||||||
# retrieve your distributions here
|
|
||||||
- name: Publish package distributions to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (riscemu)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
@ -1,12 +0,0 @@
|
|||||||
# See https://pre-commit.com for more information
|
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v3.2.0
|
|
||||||
hooks:
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 23.3.0
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
@ -1,52 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## 2.1.1
|
|
||||||
|
|
||||||
- Bugfix: Fix some errors in the RV32F implementation (thanks @adutilleul)
|
|
||||||
- Bugfix: Fix how floats are printed in the register view (thanks @KGrykiel)
|
|
||||||
- Bugfix: Fix missing support for infinite registers in load/store ins (thanks @superlopuh)
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
- Added a very basic libc containing a `crt0.s`, and a few functions
|
|
||||||
such as `malloc`, `rand`, and `memcpy`.
|
|
||||||
- Added a subset of the `mmap2` syscall (code 192) to allocate new memory
|
|
||||||
- Refactored the launching code to improve using riscemu from code
|
|
||||||
- Added an option to start with the provided libc: `-o libc`
|
|
||||||
- Added floating point support (enabled by default). The RV32F extension is now available
|
|
||||||
|
|
||||||
## 2.0.5
|
|
||||||
|
|
||||||
- Added unlimited register mode with `-o unlimited_regs`
|
|
||||||
|
|
||||||
## 2.0.4
|
|
||||||
|
|
||||||
- Bugfix: fix a sign issue in instruction parsing for `rd, rs, rs` format
|
|
||||||
- Bugfix: respect `conf.debug_instruction` setting
|
|
||||||
|
|
||||||
## 2.0.3 - 2022-04-18
|
|
||||||
|
|
||||||
- Syscalls: cleaned up formatting and added instructions for extensions
|
|
||||||
- Parser: fixed error when labels where used outside of sections
|
|
||||||
- Cleaned up and improved memory dumping code
|
|
||||||
- Fixed a bug with hex literal recognition in instruction parse code
|
|
||||||
- Fixed bug where wrong parts of section would be printed in mmu.dump()
|
|
||||||
- Removed tests for bind_twos_complement as the function is now redundant with the introduction of Int32
|
|
||||||
- Fixed address translation error for sections without symbols
|
|
||||||
- Changed verbosity level at which start and end of CPU are printed, added prints for start and stack loading
|
|
||||||
|
|
||||||
## 2.0.2
|
|
||||||
|
|
||||||
- Added implicit declaration of .text section when a file starts with assembly instructions without declaring a section first
|
|
||||||
- Fixed a regression where the cpu's exit code would no longer be the exit code of the emulator. Now the emulator exits with the cpu's exit code
|
|
||||||
- Added the changelog
|
|
||||||
|
|
||||||
## 2.0.1
|
|
||||||
|
|
||||||
- Fixed type annotations in parser code that prevented running unprivileged code
|
|
||||||
|
|
||||||
## 2.0.0
|
|
||||||
|
|
||||||
- Correct handling of 32 bit overflows and underflows
|
|
||||||
- Complete revamp of internal data structures
|
|
||||||
- Completely reworked how assembly is parsed
|
|
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"# Using RiscEmu from Python\n",
|
|
||||||
"\n",
|
|
||||||
"Here is how you can run some assembly through RiscEmu from Python.\n",
|
|
||||||
"\n",
|
|
||||||
"This example is using [this fibonacci assembly](examples/fibs.asm)."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from riscemu import RunConfig, UserModeCPU, RV32I, RV32M, AssemblyFileLoader\n",
|
|
||||||
"\n",
|
|
||||||
"cfg = RunConfig(debug_instruction=False, verbosity=50)\n",
|
|
||||||
"cpu = UserModeCPU((RV32I, RV32M), cfg)\n",
|
|
||||||
"\n",
|
|
||||||
"loader = AssemblyFileLoader.instantiate('examples/fibs.asm', [])\n",
|
|
||||||
"cpu.load_program(loader.parse())\n",
|
|
||||||
"\n",
|
|
||||||
"cpu.launch(cpu.mmu.programs[-1], True)"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.10.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 4
|
|
||||||
}
|
|
@ -1,23 +1,25 @@
|
|||||||
// Example program (c) by Anton Lydike
|
; Example program (c) by Anton Lydike
|
||||||
// this calculates the fibonacci sequence and stores it in ram
|
; this calculates the fibonacci sequence and stores it in ram
|
||||||
|
|
||||||
.data
|
.data
|
||||||
fibs: .space 56
|
fibs: .space 56
|
||||||
|
|
||||||
.text
|
.text
|
||||||
main:
|
main:
|
||||||
addi s1, zero, 0 // storage index
|
addi s1, zero, 0 ; storage index
|
||||||
addi s2, zero, 56 // last storage index
|
addi s2, zero, 56 ; last storage index
|
||||||
addi t0, zero, 1 // t0 = F_{i}
|
addi t0, zero, 1 ; t0 = F_{i}
|
||||||
addi t1, zero, 1 // t1 = F_{i+1}
|
addi t1, zero, 1 ; t1 = F_{i+1}
|
||||||
|
ebreak ; launch debugger
|
||||||
loop:
|
loop:
|
||||||
sw t0, fibs(s1) // save
|
sw t0, fibs(s1) ; save
|
||||||
add t2, t1, t0 // t2 = F_{i+2}
|
add t2, t1, t0 ; t2 = F_{i+2}
|
||||||
addi t0, t1, 0 // t0 = t1
|
addi t0, t1, 0 ; t0 = t1
|
||||||
addi t1, t2, 0 // t1 = t2
|
addi t1, t2, 0 ; t1 = t2
|
||||||
addi s1, s1, 4 // increment storage pointer
|
addi s1, s1, 4 ; increment storage pointer
|
||||||
blt s1, s2, loop // loop as long as we did not reach array length
|
blt s1, s2, loop ; loop as long as we did not reach array length
|
||||||
// exit gracefully
|
; exit gracefully
|
||||||
|
ebreak ; launch debugger
|
||||||
addi a0, zero, 0
|
addi a0, zero, 0
|
||||||
addi a7, zero, 93
|
addi a7, zero, 93
|
||||||
scall // exit with code 0
|
scall ; exit with code 0
|
@ -0,0 +1,154 @@
|
|||||||
|
; string operations in RISC-V Assembly
|
||||||
|
; All function restore all registers after use (even caller-saved ones)
|
||||||
|
|
||||||
|
.global NULL
|
||||||
|
; A global constant, points to the empty string
|
||||||
|
|
||||||
|
|
||||||
|
.global strlen
|
||||||
|
; size_t libstr_strlen(char* str)
|
||||||
|
; return the length of str
|
||||||
|
|
||||||
|
|
||||||
|
.global strncpy
|
||||||
|
; char *strncpy(char *dest, const char *src, size_t n)
|
||||||
|
; copy n bytes from source into dest. If source ends before n bytes, the rest is filled with null-bytes
|
||||||
|
; returns pointer to dest
|
||||||
|
|
||||||
|
|
||||||
|
.global strcpy
|
||||||
|
; char *strncpy(char *dest, const char *src)
|
||||||
|
; copy string src into dest, including null terminator
|
||||||
|
; returns pointer to dest
|
||||||
|
|
||||||
|
|
||||||
|
.global memchr
|
||||||
|
; void *memchr(const void *str, char c, size_t n)
|
||||||
|
; search vor the first occurance of c in str
|
||||||
|
; returns a pointer to the first occurance, or NULL
|
||||||
|
|
||||||
|
|
||||||
|
.global memset
|
||||||
|
; void *memset(void *str, char c, size_t n)
|
||||||
|
; copies the character c to the first n characters of str.
|
||||||
|
|
||||||
|
|
||||||
|
;.global memcmp
|
||||||
|
;.global memcpy
|
||||||
|
;.global strcat
|
||||||
|
|
||||||
|
; Create NullPtr constant
|
||||||
|
.set NULL, 0x00
|
||||||
|
|
||||||
|
|
||||||
|
.text
|
||||||
|
strlen:
|
||||||
|
; size_t strlen(char* str)
|
||||||
|
; push s1, s2 to the stack
|
||||||
|
sw s1, sp, -4
|
||||||
|
sw s2, sp, -8
|
||||||
|
; since no subroutines are called, we don't need to increment or decrement the sp
|
||||||
|
addi s2, zero, -1 ; length (since null byte is counted by this method, we return len - 1)
|
||||||
|
__strlen_loop:
|
||||||
|
lb s1, a0, 0 ; read character
|
||||||
|
addi s2, s2, 1 ; increment number bytes read
|
||||||
|
addi a0, a0, 1
|
||||||
|
bne s1, zero, __strlen_loop
|
||||||
|
; we are done, set return value in a0
|
||||||
|
add a0, zero, s2
|
||||||
|
; pop s1, s2, from stack
|
||||||
|
lw s1, sp, -4
|
||||||
|
lw s2, sp, -8
|
||||||
|
ret
|
||||||
|
|
||||||
|
strncpy:
|
||||||
|
; char *strncpy(char *dest, const char *src, size_t n)
|
||||||
|
; copy size bytes from source to dest
|
||||||
|
sw s1, sp, -4 ; push s1 to the stack
|
||||||
|
sw s2, sp, -8 ; push s1 to the stack
|
||||||
|
add s1, a0, zero ; save dest pointer for return
|
||||||
|
__strncpy_loop:
|
||||||
|
beq a2, zero, __strncpy_end
|
||||||
|
; copy byte
|
||||||
|
lb s2, a1, 0 ; read first byte from src
|
||||||
|
sb s2, a0, 0 ; write first byte to dest
|
||||||
|
; increment pointers
|
||||||
|
addi a0, a0, 1
|
||||||
|
addi a1, a1, 1
|
||||||
|
; one less byte to copy
|
||||||
|
addi a2, a2, -1
|
||||||
|
; if we read the terminating byte, jump to fill code
|
||||||
|
beq s2, zero, __strncpy_fill
|
||||||
|
; otherwise continue copying
|
||||||
|
j __strncpy_loop
|
||||||
|
__strncpy_fill:
|
||||||
|
; fill remaining space with 0 bytes
|
||||||
|
; if no bytes left, stop filling
|
||||||
|
beq a2, zero, __strncpy_end
|
||||||
|
sb zero, a0, 0
|
||||||
|
addi a0, a0, 1
|
||||||
|
addi a2, a2, -1
|
||||||
|
j __strncpy_fill
|
||||||
|
__strncpy_end:
|
||||||
|
; set return value
|
||||||
|
add a0, zero, s1
|
||||||
|
; pop s1, s2 from stack
|
||||||
|
lw s1, sp, -4
|
||||||
|
lw s2, sp, -8
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
strcpy:
|
||||||
|
; char *strncpy(char *dest, const char *src)
|
||||||
|
sw s1, sp, -4 ; push s1 to the stack
|
||||||
|
sw s2, sp, -8 ; push s1 to the stack
|
||||||
|
add s1, a0, zero ; save dest pointer for return
|
||||||
|
__strcpy_loop:
|
||||||
|
; copy byte
|
||||||
|
lb s2, a1, 0 ; read first byte from src
|
||||||
|
sb s2, a0, 0 ; write first byte to dest
|
||||||
|
; increment pointers
|
||||||
|
addi a0, a0, 1
|
||||||
|
addi a1, a1, 1
|
||||||
|
bne s2, zero, __strcpy_loop
|
||||||
|
; we are done copying, return
|
||||||
|
; set return value
|
||||||
|
add a0, zero, s1
|
||||||
|
; pop s1, s2 from stack
|
||||||
|
lw s1, sp, -4
|
||||||
|
lw s2, sp, -8
|
||||||
|
ret
|
||||||
|
|
||||||
|
memchr:
|
||||||
|
; void *memchr(const void *str, char c, size_t n)
|
||||||
|
sw s1, sp, -4 ; push s1 to the stack
|
||||||
|
__memchr_loop:
|
||||||
|
beq a2, zero, __memchr_ret_null
|
||||||
|
lb s1, a0, 0
|
||||||
|
addi a0, a0, 1 ; let a0 point to the next byte
|
||||||
|
addi a2, a2, -1 ; decrement bytes to copy by 1
|
||||||
|
bne s1, s2, __memchr_loop
|
||||||
|
; return pointer to prev byte (as the prev byte actually matched a1)
|
||||||
|
addi a0, a0, -1
|
||||||
|
; pop s1, s2 from stack
|
||||||
|
lw s1, sp, -4
|
||||||
|
ret
|
||||||
|
__memchr_ret_null:
|
||||||
|
; nothing found, return nullptr
|
||||||
|
addi a0, zero, NULL
|
||||||
|
lw s1, sp, -4
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
memset:
|
||||||
|
; void *memset(void *str, char c, size_t n)
|
||||||
|
__memset_loop:
|
||||||
|
beq a2, zero, __memset_ret
|
||||||
|
sb a1, a0, 0
|
||||||
|
addi a0, a0, 1
|
||||||
|
addi a2, a2, -1
|
||||||
|
j __memset_loop
|
||||||
|
__memset_ret:
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
|||||||
// example of a simple memory allocation
|
|
||||||
// we use the mmap2 syscall for this
|
|
||||||
|
|
||||||
.text
|
|
||||||
// call mmap2
|
|
||||||
li a0, 0 // addr = 0, let OS choose address
|
|
||||||
li a1, 4096 // size
|
|
||||||
li a2, 3 // PROT_READ | PROT_WRITE
|
|
||||||
li a3, 5 // MAP_PRIVATE | MAP_ANONYMOUS
|
|
||||||
li a7, SCALL_MMAP2
|
|
||||||
ecall // invoke syscall
|
|
||||||
|
|
||||||
li t0, -1 // exit if unsuccessful
|
|
||||||
beq a0, t0, _exit
|
|
||||||
|
|
||||||
// print address
|
|
||||||
print.uhex a0
|
|
||||||
# we can look at the state of the mmu here:
|
|
||||||
ebreak
|
|
||||||
# > mmu.sections
|
|
||||||
# InstructionMemorySection[.text] at 0x00000100
|
|
||||||
# BinaryDataMemorySection[.stack] at 0x00000170
|
|
||||||
# BinaryDataMemorySection[.data.runtime-allocated] at 0x00080170
|
|
||||||
|
|
||||||
sw t0, 144(a0)
|
|
||||||
sw t0, 0(a0)
|
|
||||||
sw t0, 8(a0)
|
|
||||||
sw t0, 16(a0)
|
|
||||||
sw t0, 32(a0)
|
|
||||||
sw t0, 64(a0)
|
|
||||||
sw t0, 128(a0)
|
|
||||||
sw t0, 256(a0)
|
|
||||||
sw t0, 512(a0)
|
|
||||||
sw t0, 1024(a0)
|
|
||||||
sw t0, 2048(a0)
|
|
||||||
sw t0, 4000(a0)
|
|
||||||
|
|
||||||
lw t1, 128(a0)
|
|
||||||
print.uhex t0
|
|
||||||
ebreak
|
|
||||||
|
|
||||||
_exit:
|
|
||||||
li a7, 93
|
|
||||||
ecall
|
|
@ -1,11 +0,0 @@
|
|||||||
main:
|
|
||||||
li a1, 1084227584
|
|
||||||
li a2, 1082130432
|
|
||||||
fcvt.s.wu ft0, a1
|
|
||||||
fcvt.s.wu ft1, a2
|
|
||||||
fmul.s ft6, ft0, ft1
|
|
||||||
print.float ft6
|
|
||||||
// exit gracefully
|
|
||||||
addi a0, zero, 0
|
|
||||||
addi a7, zero, 93
|
|
||||||
scall // exit with code 0
|
|
@ -1,20 +0,0 @@
|
|||||||
.data
|
|
||||||
my_data:
|
|
||||||
.word 0x11223344, 0x55667788, 0x9900aabb, 0xccddeeff
|
|
||||||
|
|
||||||
.text
|
|
||||||
main:
|
|
||||||
// load base address into t0
|
|
||||||
la t0, my_data
|
|
||||||
// begin loading words and printing them
|
|
||||||
lw a0, 0(t0)
|
|
||||||
print.uhex a0
|
|
||||||
lw a0, 4(t0)
|
|
||||||
print.uhex a0
|
|
||||||
lw a0, 8(t0)
|
|
||||||
print.uhex a0
|
|
||||||
lw a0, 12(t0)
|
|
||||||
print.uhex a0
|
|
||||||
// exit
|
|
||||||
li a7, 93
|
|
||||||
ecall
|
|
@ -1,29 +0,0 @@
|
|||||||
# RiscEmu LibC
|
|
||||||
|
|
||||||
This is a very basic implementation of libc in risc-v assembly, meant specifically for the riscemu emulator.
|
|
||||||
|
|
||||||
This is currently very incomplete, only a handful of methods are implemented, and most of them pretty basic.
|
|
||||||
|
|
||||||
## Contents:
|
|
||||||
|
|
||||||
### `stdlib.s`
|
|
||||||
|
|
||||||
Basic implementations of:
|
|
||||||
|
|
||||||
- `malloc`/`free` (that leaks memory)
|
|
||||||
- `rand`/`srand` (using xorshift)
|
|
||||||
- `exit`/`atexit` (supporting up to 8 exit handlers)
|
|
||||||
|
|
||||||
### `string.s`
|
|
||||||
|
|
||||||
Somewhat nice implementations of:
|
|
||||||
|
|
||||||
- `strlen`
|
|
||||||
- `strncpy`
|
|
||||||
- `strcpy`
|
|
||||||
- `memchr`
|
|
||||||
- `memset` (very basic byte-by-byte copy)
|
|
||||||
|
|
||||||
## Correctness:
|
|
||||||
|
|
||||||
This library is only lightly tested, so be careful and report bugs when you find them!
|
|
@ -1,14 +0,0 @@
|
|||||||
// A minimal crt0.s that works along the stdlib.s file provided to give
|
|
||||||
// some resemblance of a functioning compilation target :)
|
|
||||||
//
|
|
||||||
// Copyright (c) 2023 Anton Lydike
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
.text
|
|
||||||
|
|
||||||
.globl _start
|
|
||||||
_start:
|
|
||||||
// TODO: read argc, argv from a0, a1
|
|
||||||
// maybe even find envptr?
|
|
||||||
jal main
|
|
||||||
jal exit
|
|
@ -1,178 +0,0 @@
|
|||||||
// A very basic implementation of a stdlib.h but in assembly.
|
|
||||||
// should(tm) work with riscemu.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2023 Anton Lydike
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
.data
|
|
||||||
|
|
||||||
_rand_seed:
|
|
||||||
.word 0x76767676
|
|
||||||
_atexit_calls:
|
|
||||||
// leave room for 8 atexit handlers here for now
|
|
||||||
.word 0x00, 0x00, 0x00, 0x00
|
|
||||||
.word 0x00, 0x00, 0x00, 0x00
|
|
||||||
_atexit_count:
|
|
||||||
.word 0x00
|
|
||||||
|
|
||||||
_malloc_base_ptr:
|
|
||||||
// first word is a pointer to some space
|
|
||||||
// second word is the offset inside that space
|
|
||||||
// space is always MALLOC_PAGE_SIZE bytes
|
|
||||||
.word 0x00, 0x00
|
|
||||||
.equ MALLOC_PAGE_SIZE 4069
|
|
||||||
|
|
||||||
.text
|
|
||||||
|
|
||||||
// malloc/free
|
|
||||||
|
|
||||||
.globl malloc
|
|
||||||
.globl free
|
|
||||||
|
|
||||||
// malloc(size_t size)
|
|
||||||
malloc:
|
|
||||||
// set aside size in s0
|
|
||||||
sw s0, -4(sp)
|
|
||||||
mv a0, s0
|
|
||||||
la t0, _malloc_base_ptr
|
|
||||||
lw t1, 0(t0)
|
|
||||||
beq t1, zero, _malloc_init
|
|
||||||
_malloc_post_init:
|
|
||||||
// if we are here, we always have
|
|
||||||
// t0 = (&_malloc_base_ptr)
|
|
||||||
// t1 = *(&_malloc_base_ptr)
|
|
||||||
// new we load
|
|
||||||
// t2 = base_ptr_offset
|
|
||||||
lw t2, 4(t0)
|
|
||||||
// add allocated size to offset
|
|
||||||
add t2, t2, s0
|
|
||||||
// check for overflow
|
|
||||||
li t4, MALLOC_PAGE_SIZE
|
|
||||||
bge t2, t4, _malloc_fail
|
|
||||||
// save the new offset
|
|
||||||
sw t2, 4(t0)
|
|
||||||
// calculate base_ptr + offset
|
|
||||||
add a0, t2, t1
|
|
||||||
// return that
|
|
||||||
lw s0, -4(sp)
|
|
||||||
ret
|
|
||||||
|
|
||||||
_malloc_init:
|
|
||||||
// call mmap2()
|
|
||||||
li a0, 0 // addr = 0, let OS choose address
|
|
||||||
li a1, 4096 // size
|
|
||||||
li a2, 3 // PROT_READ | PROT_WRITE
|
|
||||||
li a3, 5 // MAP_PRIVATE | MAP_ANONYMOUS
|
|
||||||
li a7, SCALL_MMAP2
|
|
||||||
ecall // invoke syscall
|
|
||||||
// check for error code
|
|
||||||
li t0, -1
|
|
||||||
beq a0, t0, _malloc_fail
|
|
||||||
// if succeeded, load &_malloc_base_ptr
|
|
||||||
la t0, _malloc_base_ptr
|
|
||||||
// put value of _malloc_base_ptr into t1
|
|
||||||
mv a0, t1
|
|
||||||
// save base ptr to _malloc_base_ptr
|
|
||||||
sw t1, 0(t0)
|
|
||||||
// jump to post_init
|
|
||||||
j _malloc_post_init
|
|
||||||
_malloc_fail:
|
|
||||||
li a0, 0
|
|
||||||
ret
|
|
||||||
|
|
||||||
// free is a nop, that's valid, but not very good^^
|
|
||||||
free:
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// exit, atexit
|
|
||||||
.globl exit
|
|
||||||
.globl atexit
|
|
||||||
|
|
||||||
// we can happily use saved registers here because we don't care at all if we
|
|
||||||
// destroy the calling registers. This is __noreturn__ anyways!
|
|
||||||
// register layout:
|
|
||||||
// s0 = &_atexit_count
|
|
||||||
// s2 = &_atexit_calls
|
|
||||||
// s1 = updated value of atexit
|
|
||||||
// s3 = exit code
|
|
||||||
exit:
|
|
||||||
// save exit code to s3
|
|
||||||
mv s3, a0
|
|
||||||
_exit_start:
|
|
||||||
la s0, _atexit_count // s0 = &_atexit_count
|
|
||||||
lw s1, 0(s0) // s1 = *(&_atexit_count)
|
|
||||||
// exit if no atexit() calls remain
|
|
||||||
beq s1, zero, _exit
|
|
||||||
// decrement
|
|
||||||
addi s1, s1, -4 // s1--
|
|
||||||
// save decremented value
|
|
||||||
sw s1, 0(s0) // _atexit_count = s1
|
|
||||||
li s2, _atexit_calls
|
|
||||||
add s1, s1, s2 // s1 = &_atexit_calls + (s1)
|
|
||||||
lw s1, 0(s1) // s1 = *s1
|
|
||||||
la ra, _exit_start // set ra up to point to exit
|
|
||||||
jalr zero, s1, 0 // jump to address in s1
|
|
||||||
// jalr will call the other function, which will then return back
|
|
||||||
// to the beginning of exit.
|
|
||||||
_exit:
|
|
||||||
mv a0, s3
|
|
||||||
li a7, 93
|
|
||||||
ecall
|
|
||||||
|
|
||||||
// atexit a0 = funcptr
|
|
||||||
atexit:
|
|
||||||
sw t0, -4(sp)
|
|
||||||
sw t2, -8(sp)
|
|
||||||
// load _atexit_count
|
|
||||||
la t0, _atexit_count
|
|
||||||
lw t2, 0(t0)
|
|
||||||
// if >= 8, fail
|
|
||||||
li t1, 8
|
|
||||||
bge t2, t1, _atexit_fail
|
|
||||||
// increment atexit_count by 4 (one word)
|
|
||||||
addi t2, t2, 4
|
|
||||||
sw t2, 0(t0)
|
|
||||||
// load address of _atexit_calls
|
|
||||||
la t0, _atexit_calls
|
|
||||||
// add new _atexit_count to _atexit_calls
|
|
||||||
add t0, t0, t2
|
|
||||||
sw a0, -4(t0)
|
|
||||||
li a0, 0
|
|
||||||
lw t0, -4(sp)
|
|
||||||
lw t2, -8(sp)
|
|
||||||
ret
|
|
||||||
|
|
||||||
_atexit_fail:
|
|
||||||
li a0, -1
|
|
||||||
lw s0, -4(sp)
|
|
||||||
lw s1, -8(sp)
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// rand, srand
|
|
||||||
|
|
||||||
.globl rand
|
|
||||||
.globl srand
|
|
||||||
|
|
||||||
// simple xorshift rand implementation
|
|
||||||
rand:
|
|
||||||
// load seed
|
|
||||||
la t1, _rand_seed
|
|
||||||
lw a0, 0(t1)
|
|
||||||
// three rounds of shifts:
|
|
||||||
sll a0, t0, 13 // x ^= x << 13;
|
|
||||||
srl a0, t0, 17 // x ^= x >> 17;
|
|
||||||
sll a0, t0, 5 // x ^= x << 5;
|
|
||||||
sw a0, 0(t1)
|
|
||||||
ret
|
|
||||||
|
|
||||||
srand:
|
|
||||||
la t1, _rand_seed
|
|
||||||
sw a0, 0(t1)
|
|
||||||
ret
|
|
@ -1,154 +0,0 @@
|
|||||||
// string operations in RISC-V Assembly
|
|
||||||
//
|
|
||||||
// Copyright (c) 2023 Anton Lydike
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// Create NullPtr constant
|
|
||||||
.equ NULL, 0x00
|
|
||||||
.global NULL
|
|
||||||
|
|
||||||
|
|
||||||
.global strlen
|
|
||||||
// size_t libstr_strlen(char* str)
|
|
||||||
// return the length of str
|
|
||||||
|
|
||||||
|
|
||||||
.global strncpy
|
|
||||||
// char *strncpy(char *dest, const char *src, size_t n)
|
|
||||||
// copy n bytes from source into dest. If source ends before n bytes, the rest is filled with null-bytes
|
|
||||||
// returns pointer to dest
|
|
||||||
|
|
||||||
|
|
||||||
.global strcpy
|
|
||||||
// char *strncpy(char *dest, const char *src)
|
|
||||||
// copy string src into dest, including null terminator
|
|
||||||
// returns pointer to dest
|
|
||||||
|
|
||||||
|
|
||||||
.global memchr
|
|
||||||
// void *memchr(const void *str, char c, size_t n)
|
|
||||||
// search vor the first occurance of c in str
|
|
||||||
// returns a pointer to the first occurance, or NULL
|
|
||||||
|
|
||||||
|
|
||||||
.global memset
|
|
||||||
// void *memset(void *str, char c, size_t n)
|
|
||||||
// copies the character c to the first n characters of str.
|
|
||||||
|
|
||||||
|
|
||||||
// missing implementations
|
|
||||||
//.global memcmp
|
|
||||||
//.global memcpy
|
|
||||||
//.global strcat
|
|
||||||
|
|
||||||
|
|
||||||
.text
|
|
||||||
strlen:
|
|
||||||
// size_t strlen(char* str)
|
|
||||||
// push s1, s2 to the stack
|
|
||||||
sw s1, sp, -4
|
|
||||||
sw s2, sp, -8
|
|
||||||
// since no subroutines are called, we don't need to increment or decrement the sp
|
|
||||||
addi s2, zero, -1 // length (since null byte is counted by this method, we return len - 1)
|
|
||||||
__strlen_loop:
|
|
||||||
lb s1, a0, 0 // read character
|
|
||||||
addi s2, s2, 1 // increment number bytes read
|
|
||||||
addi a0, a0, 1
|
|
||||||
bne s1, zero, __strlen_loop
|
|
||||||
// we are done, set return value in a0
|
|
||||||
add a0, zero, s2
|
|
||||||
// pop s1, s2, from stack
|
|
||||||
lw s1, sp, -4
|
|
||||||
lw s2, sp, -8
|
|
||||||
ret
|
|
||||||
|
|
||||||
strncpy:
|
|
||||||
// char *strncpy(char *dest, const char *src, size_t n)
|
|
||||||
// copy size bytes from source to dest
|
|
||||||
sw s1, sp, -4 // push s1 to the stack
|
|
||||||
sw s2, sp, -8 // push s1 to the stack
|
|
||||||
add s1, a0, zero // save dest pointer for return
|
|
||||||
__strncpy_loop:
|
|
||||||
beq a2, zero, __strncpy_end
|
|
||||||
// copy byte
|
|
||||||
lb s2, a1, 0 // read first byte from src
|
|
||||||
sb s2, a0, 0 // write first byte to dest
|
|
||||||
// increment pointers
|
|
||||||
addi a0, a0, 1
|
|
||||||
addi a1, a1, 1
|
|
||||||
// one less byte to copy
|
|
||||||
addi a2, a2, -1
|
|
||||||
// if we read the terminating byte, jump to fill code
|
|
||||||
beq s2, zero, __strncpy_fill
|
|
||||||
// otherwise continue copying
|
|
||||||
j __strncpy_loop
|
|
||||||
__strncpy_fill:
|
|
||||||
// fill remaining space with 0 bytes
|
|
||||||
// if no bytes left, stop filling
|
|
||||||
beq a2, zero, __strncpy_end
|
|
||||||
sb zero, a0, 0
|
|
||||||
addi a0, a0, 1
|
|
||||||
addi a2, a2, -1
|
|
||||||
j __strncpy_fill
|
|
||||||
__strncpy_end:
|
|
||||||
// set return value
|
|
||||||
add a0, zero, s1
|
|
||||||
// pop s1, s2 from stack
|
|
||||||
lw s1, sp, -4
|
|
||||||
lw s2, sp, -8
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
strcpy:
|
|
||||||
// char *strcpy(char *dest, const char *src)
|
|
||||||
sw s1, sp, -4 // push s1 to the stack
|
|
||||||
sw s2, sp, -8 // push s1 to the stack
|
|
||||||
add s1, a0, zero // save dest pointer for return
|
|
||||||
__strcpy_loop:
|
|
||||||
// copy byte
|
|
||||||
lb s2, a1, 0 // read first byte from src
|
|
||||||
sb s2, a0, 0 // write first byte to dest
|
|
||||||
// increment pointers
|
|
||||||
addi a0, a0, 1
|
|
||||||
addi a1, a1, 1
|
|
||||||
bne s2, zero, __strcpy_loop
|
|
||||||
// we are done copying, return
|
|
||||||
// set return value
|
|
||||||
add a0, zero, s1
|
|
||||||
// pop s1, s2 from stack
|
|
||||||
lw s1, sp, -4
|
|
||||||
lw s2, sp, -8
|
|
||||||
ret
|
|
||||||
|
|
||||||
memchr:
|
|
||||||
// void *memchr(const void *str, char c, size_t n)
|
|
||||||
sw s1, sp, -4 // push s1 to the stack
|
|
||||||
andi a1, a1, 0xff // trim a1 to be byte-sized
|
|
||||||
__memchr_loop:
|
|
||||||
beq a2, zero, __memchr_ret_null
|
|
||||||
lb s1, a0, 0
|
|
||||||
addi a0, a0, 1 // let a0 point to the next byte
|
|
||||||
addi a2, a2, -1 // decrement bytes to copy by 1
|
|
||||||
bne s1, a1, __memchr_loop
|
|
||||||
// return pointer to prev byte (as the prev byte actually matched a1)
|
|
||||||
addi a0, a0, -1
|
|
||||||
// pop s1, from stack
|
|
||||||
lw s1, sp, -4
|
|
||||||
ret
|
|
||||||
__memchr_ret_null:
|
|
||||||
// nothing found, return nullptr
|
|
||||||
addi a0, zero, NULL
|
|
||||||
lw s1, sp, -4
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
memset:
|
|
||||||
// void *memset(void *str, char c, size_t n)
|
|
||||||
__memset_loop:
|
|
||||||
beq a2, zero, __memset_ret
|
|
||||||
sb a1, a0, 0
|
|
||||||
addi a0, a0, 1
|
|
||||||
addi a2, a2, -1
|
|
||||||
j __memset_loop
|
|
||||||
__memset_ret:
|
|
||||||
ret
|
|
@ -1,3 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["setuptools", "wheel"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
@ -1,5 +0,0 @@
|
|||||||
black==23.3.0
|
|
||||||
pre-commit==3.2.2
|
|
||||||
pytest==7.3.1
|
|
||||||
filecheck==0.0.23
|
|
||||||
lit==16.0.2
|
|
@ -1,138 +1,189 @@
|
|||||||
"""
|
"""
|
||||||
RiscEmu (c) 2021-2022 Anton Lydike
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
This file contains the CPU logic (not the individual instruction sets). See instructions/instruction_set.py for more info
|
This file contains the CPU logic (not the individual instruction sets). See instructions/InstructionSet.py for more info
|
||||||
on them.
|
on them.
|
||||||
"""
|
"""
|
||||||
import typing
|
import sys
|
||||||
from typing import List, Type
|
from typing import Tuple, List, Dict, Callable, Type
|
||||||
|
|
||||||
import riscemu
|
from .Tokenizer import RiscVTokenizer
|
||||||
from .config import RunConfig
|
from .Executable import MemoryFlags
|
||||||
|
from .Syscall import SyscallInterface, get_syscall_symbols
|
||||||
|
from .Exceptions import RiscemuBaseException, LaunchDebuggerException
|
||||||
from .MMU import MMU
|
from .MMU import MMU
|
||||||
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
|
from .Config import RunConfig
|
||||||
|
from .Registers import Registers
|
||||||
from .debug import launch_debug_session
|
from .debug import launch_debug_session
|
||||||
from .types.exceptions import RiscemuBaseException, LaunchDebuggerException
|
from .colors import FMT_CPU, FMT_NONE, FMT_ERROR
|
||||||
from .syscall import SyscallInterface, get_syscall_symbols
|
|
||||||
from .types import CPU, ProgramLoader, Int32, BinaryDataMemorySection
|
import riscemu
|
||||||
from .parser import AssemblyFileLoader
|
|
||||||
|
import typing
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from .instructions.instruction_set import InstructionSet
|
from . import Executable, LoadedExecutable, LoadedInstruction
|
||||||
|
from .instructions.InstructionSet import InstructionSet
|
||||||
|
|
||||||
|
|
||||||
class UserModeCPU(CPU):
|
class CPU:
|
||||||
"""
|
"""
|
||||||
This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler.
|
This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler.
|
||||||
|
|
||||||
It is initialized with a configuration and a list of instruction sets.
|
It is initialized with a configuration and a list of instruction sets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
INS_XLEN = 1
|
||||||
self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig
|
|
||||||
):
|
def __init__(self, conf: RunConfig, instruction_sets: List[Type['riscemu.InstructionSet']]):
|
||||||
"""
|
"""
|
||||||
Creates a CPU instance.
|
Creates a CPU instance.
|
||||||
|
|
||||||
|
:param conf: An instance of the current RunConfiguration
|
||||||
:param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class
|
:param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class
|
||||||
"""
|
"""
|
||||||
# setup CPU states
|
# setup CPU states
|
||||||
super().__init__(MMU(), instruction_sets, conf)
|
self.pc = 0
|
||||||
|
self.cycle = 0
|
||||||
|
self.exit: bool = False
|
||||||
|
self.exit_code: int = 0
|
||||||
|
self.conf = conf
|
||||||
|
self.active_debug = False # if a debugging session is currently runnign
|
||||||
|
|
||||||
|
self.stack: typing.Optional['riscemu.LoadedMemorySection'] = None
|
||||||
|
|
||||||
|
# setup MMU, registers and syscall handlers
|
||||||
|
self.mmu = MMU(conf)
|
||||||
|
self.regs = Registers(conf)
|
||||||
|
self.syscall_int = SyscallInterface()
|
||||||
|
|
||||||
self.exit_code = 0
|
# load all instruction sets
|
||||||
|
self.instruction_sets: List[riscemu.InstructionSet] = list()
|
||||||
|
self.instructions: Dict[str, Callable[[LoadedInstruction], None]] = dict()
|
||||||
|
for set_class in instruction_sets:
|
||||||
|
ins_set = set_class(self)
|
||||||
|
self.instructions.update(ins_set.load())
|
||||||
|
self.instruction_sets.append(ins_set)
|
||||||
|
|
||||||
# setup syscall interface
|
# provide global syscall symbols if option is set
|
||||||
self.syscall_int = SyscallInterface()
|
if conf.include_scall_symbols:
|
||||||
|
self.mmu.global_symbols.update(get_syscall_symbols())
|
||||||
|
|
||||||
# add global syscall symbols, but don't overwrite any user-defined symbols
|
def get_tokenizer(self, tokenizer_input):
|
||||||
syscall_symbols = get_syscall_symbols()
|
"""
|
||||||
syscall_symbols.update(self.mmu.global_symbols)
|
Returns a tokenizer that respects the language of the CPU
|
||||||
self.mmu.global_symbols.update(syscall_symbols)
|
|
||||||
|
|
||||||
def step(self, verbose: bool = False):
|
:param tokenizer_input: an instance of the RiscVTokenizerInput class
|
||||||
"""
|
"""
|
||||||
Execute a single instruction, then return.
|
return RiscVTokenizer(tokenizer_input, self.all_instructions())
|
||||||
|
|
||||||
|
def load(self, e: riscemu.Executable):
|
||||||
|
"""
|
||||||
|
Load an executable into Memory
|
||||||
"""
|
"""
|
||||||
if self.halted:
|
return self.mmu.load_bin(e)
|
||||||
print(
|
|
||||||
FMT_CPU
|
|
||||||
+ "[CPU] Program exited with code {}".format(self.exit_code)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
launch_debugger = False
|
def run_loaded(self, le: 'riscemu.LoadedExecutable'):
|
||||||
|
"""
|
||||||
|
Run a loaded executable
|
||||||
|
"""
|
||||||
|
self.pc = le.run_ptr
|
||||||
|
|
||||||
try:
|
if self.conf.stack_size > 0:
|
||||||
self.cycle += 1
|
self.stack = self.mmu.allocate_section("stack", self.conf.stack_size, MemoryFlags(False, False))
|
||||||
ins = self.mmu.read_ins(self.pc)
|
self.regs.set('sp', self.stack.base + self.stack.size)
|
||||||
if verbose:
|
print(FMT_CPU + '[CPU] Allocated {} bytes of stack'.format(self.stack.size) + FMT_NONE)
|
||||||
print(
|
|
||||||
FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)
|
print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(le.run_ptr, le.name) + FMT_NONE)
|
||||||
)
|
self._run()
|
||||||
self.pc += self.INS_XLEN
|
|
||||||
self.run_instruction(ins)
|
def continue_from_debugger(self, verbose=True):
|
||||||
except RiscemuBaseException as ex:
|
"""
|
||||||
if isinstance(ex, LaunchDebuggerException):
|
called from the debugger to continue running
|
||||||
# if the debugger is active, raise the exception to
|
|
||||||
if self.debugger_active:
|
:param verbose: If True, will print each executed instruction to STDOUT
|
||||||
raise ex
|
"""
|
||||||
|
self._run(verbose)
|
||||||
print(FMT_CPU + "[CPU] Debugger launch requested!" + FMT_NONE)
|
|
||||||
launch_debugger = True
|
def step(self):
|
||||||
else:
|
"""
|
||||||
|
Execute a single instruction, then return.
|
||||||
|
"""
|
||||||
|
if self.exit:
|
||||||
|
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.cycle += 1
|
||||||
|
ins = self.mmu.read_ins(self.pc)
|
||||||
|
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||||
|
self.pc += self.INS_XLEN
|
||||||
|
self.run_instruction(ins)
|
||||||
|
except LaunchDebuggerException:
|
||||||
|
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
||||||
|
except RiscemuBaseException as ex:
|
||||||
|
self.pc -= self.INS_XLEN
|
||||||
print(ex.message())
|
print(ex.message())
|
||||||
ex.print_stacktrace()
|
|
||||||
print(FMT_CPU + "[CPU] Halting due to exception!" + FMT_NONE)
|
|
||||||
self.halted = True
|
|
||||||
|
|
||||||
if launch_debugger:
|
|
||||||
launch_debug_session(self)
|
|
||||||
|
|
||||||
def run(self, verbose: bool = False):
|
|
||||||
while not self.halted:
|
|
||||||
self.step(verbose)
|
|
||||||
|
|
||||||
if self.conf.verbosity > 0:
|
|
||||||
print(
|
|
||||||
FMT_CPU
|
|
||||||
+ "[CPU] Program exited with code {}".format(self.exit_code)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
def setup_stack(self, stack_size: int = 1024 * 4) -> bool:
|
|
||||||
"""
|
|
||||||
Create program stack and populate stack pointer
|
|
||||||
:param stack_size: the size of the required stack, defaults to 4Kib
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
stack_sec = BinaryDataMemorySection(
|
|
||||||
bytearray(stack_size),
|
|
||||||
".stack",
|
|
||||||
None, # FIXME: why does a binary data memory section require a context?
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.mmu.load_section(stack_sec, fixed_position=False):
|
def _run(self, verbose=False):
|
||||||
print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE)
|
if self.pc <= 0:
|
||||||
return False
|
return False
|
||||||
|
ins = None
|
||||||
|
try:
|
||||||
|
while not self.exit:
|
||||||
|
self.cycle += 1
|
||||||
|
ins = self.mmu.read_ins(self.pc)
|
||||||
|
if verbose:
|
||||||
|
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||||
|
self.pc += self.INS_XLEN
|
||||||
|
self.run_instruction(ins)
|
||||||
|
except RiscemuBaseException as ex:
|
||||||
|
if not isinstance(ex, LaunchDebuggerException):
|
||||||
|
print(FMT_ERROR + "[CPU] excpetion caught at 0x{:08X}: {}:".format(self.pc - 1, ins) + FMT_NONE)
|
||||||
|
print(ex.message())
|
||||||
|
self.pc -= self.INS_XLEN
|
||||||
|
|
||||||
|
if self.active_debug:
|
||||||
|
print(FMT_CPU + "[CPU] Returning to debugger!" + FMT_NONE)
|
||||||
|
return
|
||||||
|
if self.conf.debug_on_exception:
|
||||||
|
launch_debug_session(self, self.mmu, self.regs, "Exception encountered, launching debug:")
|
||||||
|
|
||||||
|
if self.exit:
|
||||||
|
print()
|
||||||
|
print(FMT_CPU + "Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
||||||
|
sys.exit(self.exit_code)
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
print(FMT_CPU + "Program stopped without exiting - perhaps you stopped the debugger?" + FMT_NONE)
|
||||||
|
|
||||||
|
def run_instruction(self, ins: 'LoadedInstruction'):
|
||||||
|
"""
|
||||||
|
Execute a single instruction
|
||||||
|
|
||||||
self.regs.set("sp", Int32(stack_sec.base + stack_sec.size))
|
:param ins: The instruction to execute
|
||||||
|
"""
|
||||||
if self.conf.verbosity > 1:
|
if ins.name in self.instructions:
|
||||||
print(
|
self.instructions[ins.name](ins)
|
||||||
FMT_CPU
|
else:
|
||||||
+ "[CPU] Created stack of size {} at 0x{:x}".format(
|
# this should never be reached, as unknown instructions are imparseable
|
||||||
stack_size, stack_sec.base
|
raise RuntimeError("Unknown instruction: {}".format(ins))
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
def all_instructions(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
Return a list of all instructions this CPU can execute.
|
||||||
|
"""
|
||||||
|
return list(self.instructions.keys())
|
||||||
|
|
||||||
@classmethod
|
def __repr__(self):
|
||||||
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
|
"""
|
||||||
return [AssemblyFileLoader]
|
Returns a representation of the CPU and some of its state.
|
||||||
|
"""
|
||||||
|
return "{}(pc=0x{:08X}, cycle={}, exit={}, instructions={})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.pc,
|
||||||
|
self.cycle,
|
||||||
|
self.exit,
|
||||||
|
" ".join(s.name for s in self.instruction_sets)
|
||||||
|
)
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
from .colors import *
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from .Executable import LoadedInstruction
|
||||||
|
|
||||||
|
|
||||||
|
class RiscemuBaseException(BaseException):
|
||||||
|
@abstractmethod
|
||||||
|
def message(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Parsing exceptions:
|
||||||
|
|
||||||
|
class ParseException(RiscemuBaseException):
|
||||||
|
def __init__(self, msg, data=None):
|
||||||
|
super().__init__()
|
||||||
|
self.msg = msg
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
def ASSERT_EQ(a1, a2):
|
||||||
|
if a1 != a2:
|
||||||
|
raise ParseException("ASSERTION_FAILED: Expected elements to be equal!", (a1, a2))
|
||||||
|
|
||||||
|
|
||||||
|
def ASSERT_LEN(a1, size):
|
||||||
|
if len(a1) != size:
|
||||||
|
raise ParseException("ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), (a1, size))
|
||||||
|
|
||||||
|
|
||||||
|
def ASSERT_NOT_NULL(a1):
|
||||||
|
if a1 is None:
|
||||||
|
raise ParseException("ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,))
|
||||||
|
|
||||||
|
|
||||||
|
def ASSERT_NOT_IN(a1, a2):
|
||||||
|
if a1 in a2:
|
||||||
|
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
|
||||||
|
|
||||||
|
|
||||||
|
def ASSERT_IN(a1, a2):
|
||||||
|
if a1 not in a2:
|
||||||
|
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
|
||||||
|
|
||||||
|
|
||||||
|
class LinkerException(RiscemuBaseException):
|
||||||
|
def __init__(self, msg, data):
|
||||||
|
self.msg = msg
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
# MMU Exceptions
|
||||||
|
|
||||||
|
class MemoryAccessException(RiscemuBaseException):
|
||||||
|
def __init__(self, msg, addr, size, op):
|
||||||
|
super(MemoryAccessException, self).__init__()
|
||||||
|
self.msg = msg
|
||||||
|
self.addr = addr
|
||||||
|
self.size = size
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_MEM + "{}(During {} at 0x{:08x} of size {}: {})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.op,
|
||||||
|
self.addr,
|
||||||
|
self.size,
|
||||||
|
self.msg
|
||||||
|
) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
class OutOfMemoryException(RiscemuBaseException):
|
||||||
|
def __init__(self, action):
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_MEM + '{}(Ran out of memory during {})'.format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.action
|
||||||
|
) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAllocationException(RiscemuBaseException):
|
||||||
|
def __init__(self, msg, name, size, flags):
|
||||||
|
self.msg = msg
|
||||||
|
self.name = name
|
||||||
|
self.size = size
|
||||||
|
self.flags = flags
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_MEM + '{}[{}](name={}, size={}, flags={})'.format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.msg,
|
||||||
|
self.name,
|
||||||
|
self.size,
|
||||||
|
self.flags
|
||||||
|
)
|
||||||
|
|
||||||
|
# CPU Exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class UnimplementedInstruction(RiscemuBaseException):
|
||||||
|
def __init__(self, ins: 'LoadedInstruction'):
|
||||||
|
self.ins = ins
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_CPU + "{}({})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
repr(self.ins)
|
||||||
|
) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRegisterException(RiscemuBaseException):
|
||||||
|
def __init__(self, reg):
|
||||||
|
self.reg = reg
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_CPU + "{}(Invalid register {})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.reg
|
||||||
|
) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSyscallException(RiscemuBaseException):
|
||||||
|
def __init__(self, scall):
|
||||||
|
self.scall = scall
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return FMT_SYSCALL + "{}(Invalid syscall: {})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.scall
|
||||||
|
) + FMT_NONE
|
||||||
|
|
||||||
|
|
||||||
|
def INS_NOT_IMPLEMENTED(ins):
|
||||||
|
raise UnimplementedInstruction(ins)
|
||||||
|
|
||||||
|
|
||||||
|
class NumberFormatException(RiscemuBaseException):
|
||||||
|
def __init__(self, msg):
|
||||||
|
super().__init__()
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return "{}({})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.msg
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# this exception is not printed and simply signals that an interactive debugging session is
|
||||||
|
class LaunchDebuggerException(RiscemuBaseException):
|
||||||
|
def message(self):
|
||||||
|
return ""
|
@ -0,0 +1,319 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
This file holds Executable and LoadedExecutable classes as well as loading and some linking code.
|
||||||
|
|
||||||
|
FIXME: refactor this code into muliple files
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, List, Tuple, Union, Optional
|
||||||
|
from .Exceptions import *
|
||||||
|
from .helpers import *
|
||||||
|
from math import log
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from .Tokenizer import RiscVInstructionToken
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MemoryFlags:
|
||||||
|
read_only: bool
|
||||||
|
executable: bool
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}({},{})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
'ro' if self.read_only else 'rw',
|
||||||
|
'x' if self.executable else '-'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MemorySection:
|
||||||
|
name: str
|
||||||
|
flags: MemoryFlags
|
||||||
|
size: int = 0
|
||||||
|
content: List[bytearray] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add(self, data: bytearray):
|
||||||
|
self.content.append(data)
|
||||||
|
self.size += len(data)
|
||||||
|
|
||||||
|
def continuous_content(self, parent: 'LoadedExecutable'):
|
||||||
|
"""
|
||||||
|
converts the content into one continuous bytearray
|
||||||
|
"""
|
||||||
|
if self.size == 0:
|
||||||
|
return bytearray(0)
|
||||||
|
content = self.content[0]
|
||||||
|
for b in self.content[1:]:
|
||||||
|
content += b
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InstructionMemorySection(MemorySection):
|
||||||
|
content: List['RiscVInstructionToken'] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add_insn(self, insn: 'RiscVInstructionToken'):
|
||||||
|
self.content.append(insn)
|
||||||
|
self.size += 1
|
||||||
|
|
||||||
|
def continuous_content(self, parent: 'LoadedExecutable'):
|
||||||
|
return [
|
||||||
|
LoadedInstruction(ins.instruction, ins.args, parent)
|
||||||
|
for ins in self.content
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class Executable:
|
||||||
|
run_ptr: Tuple[str, int]
|
||||||
|
sections: Dict[str, MemorySection]
|
||||||
|
symbols: Dict[str, Tuple[str, int]]
|
||||||
|
exported_symbols: List[str]
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}(sections = {}, symbols = {}, run_ptr = {}, globals={})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
" ".join(self.sections.keys()),
|
||||||
|
" ".join(self.symbols.keys()),
|
||||||
|
self.run_ptr,
|
||||||
|
",".join(self.exported_symbols)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
### LOADING CODE
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LoadedInstruction:
|
||||||
|
"""
|
||||||
|
An instruction which is loaded into memory. It knows the binary it belongs to to resolve symbols
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
args: List[str]
|
||||||
|
bin: 'LoadedExecutable'
|
||||||
|
|
||||||
|
def get_imm(self, num: int):
|
||||||
|
"""
|
||||||
|
parse and get immediate argument
|
||||||
|
"""
|
||||||
|
if len(self.args) <= num:
|
||||||
|
raise ParseException("Instruction {} expected argument at {} (args: {})".format(self.name, num, self.args))
|
||||||
|
arg = self.args[num]
|
||||||
|
# look up symbols
|
||||||
|
if self.bin.has_symb(arg):
|
||||||
|
return self.bin.lookup_symbol(arg)
|
||||||
|
return parse_numeric_argument(arg)
|
||||||
|
|
||||||
|
def get_imm_reg(self, num: int):
|
||||||
|
"""
|
||||||
|
parse and get an argument imm(reg)
|
||||||
|
"""
|
||||||
|
if len(self.args) <= num:
|
||||||
|
raise ParseException("Instruction {} expected argument at {} (args: {})".format(self.name, num, self.args))
|
||||||
|
arg = self.args[num]
|
||||||
|
ASSERT_IN("(", arg)
|
||||||
|
imm, reg = arg[:-1].split("(")
|
||||||
|
if self.bin.has_symb(imm):
|
||||||
|
return self.bin.lookup_symbol(imm), reg
|
||||||
|
return parse_numeric_argument(imm), reg
|
||||||
|
|
||||||
|
def get_reg(self, num: int):
|
||||||
|
"""
|
||||||
|
parse and get an register argument
|
||||||
|
"""
|
||||||
|
if len(self.args) <= num:
|
||||||
|
raise ParseException("Instruction {} expected argument at {} (args: {})".format(self.name, num, self.args))
|
||||||
|
return self.args[num]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{} {}".format(self.name, ", ".join(self.args))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LoadedMemorySection:
|
||||||
|
"""
|
||||||
|
A section which is loaded into memory
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
base: int
|
||||||
|
size: int
|
||||||
|
content: Union[List[LoadedInstruction], bytearray] = field(repr=False)
|
||||||
|
flags: MemoryFlags
|
||||||
|
owner: str
|
||||||
|
|
||||||
|
def read(self, offset: int, size: int):
|
||||||
|
if offset < 0:
|
||||||
|
raise MemoryAccessException('Invalid offset {}'.format(offset), self.base + offset, size, 'read')
|
||||||
|
if offset + size > self.size:
|
||||||
|
raise MemoryAccessException('Outside section boundary of section {}'.format(self.name), self.base + offset,
|
||||||
|
size, 'read')
|
||||||
|
return self.content[offset: offset + size]
|
||||||
|
|
||||||
|
def read_instruction(self, offset):
|
||||||
|
if not self.flags.executable:
|
||||||
|
raise MemoryAccessException('Section not executable!', self.base + offset, 1, 'read exec')
|
||||||
|
|
||||||
|
if offset < 0:
|
||||||
|
raise MemoryAccessException('Invalid offset {}'.format(offset), self.base + offset, 1, 'read exec')
|
||||||
|
if offset >= self.size:
|
||||||
|
raise MemoryAccessException('Outside section boundary of section {}'.format(self.name), self.base + offset,
|
||||||
|
1, 'read exec')
|
||||||
|
return self.content[offset]
|
||||||
|
|
||||||
|
def write(self, offset, size, data):
|
||||||
|
if self.flags.read_only:
|
||||||
|
raise MemoryAccessException('Section not writeable {}'.format(self.name), self.base + offset, size, 'write')
|
||||||
|
|
||||||
|
if offset < 0:
|
||||||
|
raise MemoryAccessException('Invalid offset {}'.format(offset), self.base + offset, 1, 'write')
|
||||||
|
if offset >= self.size:
|
||||||
|
raise MemoryAccessException('Outside section boundary of section {}'.format(self.name), self.base + offset,
|
||||||
|
size, 'write')
|
||||||
|
|
||||||
|
for i in range(size):
|
||||||
|
self.content[offset + i] = data[i]
|
||||||
|
|
||||||
|
def dump(self, at_addr=None, fmt='hex', max_rows=10, group=4, bytes_per_row=16, all=False):
|
||||||
|
highlight = -1
|
||||||
|
if at_addr is None:
|
||||||
|
at_addr = self.base
|
||||||
|
else:
|
||||||
|
highlight = at_addr - self.base
|
||||||
|
|
||||||
|
at_off = at_addr - self.base
|
||||||
|
start = max(align_addr(at_off - ((max_rows * bytes_per_row) // 2), 8) - 8, 0)
|
||||||
|
if all:
|
||||||
|
end = self.size
|
||||||
|
start = 0
|
||||||
|
else:
|
||||||
|
end = min(start + (max_rows * bytes_per_row), self.size)
|
||||||
|
|
||||||
|
fmt_str = " 0x{:0" + str(ceil(log(self.base + end, 16))) + "X}: {}"
|
||||||
|
|
||||||
|
if self.flags.executable:
|
||||||
|
# this section holds instructions!
|
||||||
|
start = 0 if all else max(at_off - (max_rows // 2), 0)
|
||||||
|
end = self.size if all else min(self.size, start + max_rows)
|
||||||
|
print(FMT_MEM + "{}, viewing {} instructions:".format(
|
||||||
|
self, end - start
|
||||||
|
) + FMT_NONE)
|
||||||
|
for i in range(start, end):
|
||||||
|
if i == highlight:
|
||||||
|
ins = FMT_UNDERLINE + FMT_ORANGE + repr(self.content[i]) + FMT_NONE
|
||||||
|
else:
|
||||||
|
ins = repr(self.content[i])
|
||||||
|
print(fmt_str.format(self.base + i, ins))
|
||||||
|
else:
|
||||||
|
print(FMT_MEM + "{}, viewing {} bytes:".format(
|
||||||
|
self, end - start
|
||||||
|
) + FMT_NONE)
|
||||||
|
for i in range(0, end - start, bytes_per_row):
|
||||||
|
data = self.content[start + i: min(start + i + bytes_per_row, end)]
|
||||||
|
if start + i <= highlight <= start + i + bytes_per_row:
|
||||||
|
# do hightlight here!
|
||||||
|
hi_ind = (highlight - start - i) // group
|
||||||
|
print(fmt_str.format(self.base + start + i, format_bytes(data, fmt, group, highlight=hi_ind)))
|
||||||
|
else:
|
||||||
|
print(fmt_str.format(self.base + start + i, format_bytes(data, fmt, group)))
|
||||||
|
if end == self.size:
|
||||||
|
print(FMT_MEM + "End of section!" + FMT_NONE)
|
||||||
|
else:
|
||||||
|
print(FMT_MEM + "More bytes ..." + FMT_NONE)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.name,
|
||||||
|
self.base,
|
||||||
|
self.size,
|
||||||
|
self.flags,
|
||||||
|
self.owner
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LoadedExecutable:
|
||||||
|
"""
|
||||||
|
This represents an executable which is loaded into memory at address base_addr
|
||||||
|
|
||||||
|
This is basicalle the "loader" in normal system environments
|
||||||
|
It initializes the stack and heap
|
||||||
|
|
||||||
|
It still holds a symbol table, that is not accessible memory since I don't want to deal with
|
||||||
|
binary strings in memory etc.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
base_addr: int
|
||||||
|
sections_by_name: Dict[str, LoadedMemorySection]
|
||||||
|
sections: List[LoadedMemorySection]
|
||||||
|
symbols: Dict[str, int]
|
||||||
|
run_ptr: int
|
||||||
|
exported_symbols: Dict[str, int]
|
||||||
|
global_symbol_table: Dict[str, int]
|
||||||
|
|
||||||
|
def __init__(self, exe: Executable, base_addr: int, global_symbol_table: Dict[str, int]):
|
||||||
|
self.name = exe.name
|
||||||
|
self.base_addr = base_addr
|
||||||
|
self.sections = list()
|
||||||
|
self.sections_by_name = dict()
|
||||||
|
self.symbols = dict()
|
||||||
|
self.exported_symbols = dict()
|
||||||
|
self.global_symbol_table = global_symbol_table
|
||||||
|
|
||||||
|
curr = base_addr
|
||||||
|
for sec in exe.sections.values():
|
||||||
|
loaded_sec = LoadedMemorySection(
|
||||||
|
sec.name,
|
||||||
|
curr,
|
||||||
|
sec.size,
|
||||||
|
sec.continuous_content(self),
|
||||||
|
sec.flags,
|
||||||
|
self.name
|
||||||
|
)
|
||||||
|
self.sections.append(loaded_sec)
|
||||||
|
self.sections_by_name[loaded_sec.name] = loaded_sec
|
||||||
|
curr = align_addr(loaded_sec.size + curr)
|
||||||
|
|
||||||
|
for name, (sec_name, offset) in exe.symbols.items():
|
||||||
|
if sec_name == '_static_':
|
||||||
|
self.symbols[name] = offset
|
||||||
|
else:
|
||||||
|
ASSERT_IN(sec_name, self.sections_by_name)
|
||||||
|
self.symbols[name] = self.sections_by_name[sec_name].base + offset
|
||||||
|
|
||||||
|
for name in exe.exported_symbols:
|
||||||
|
self.exported_symbols[name] = self.symbols[name]
|
||||||
|
|
||||||
|
self.size = curr - base_addr
|
||||||
|
|
||||||
|
# translate run_ptr from executable
|
||||||
|
run_ptr_sec, run_ptr_off = exe.run_ptr
|
||||||
|
self.run_ptr = self.sections_by_name[run_ptr_sec].base + run_ptr_off
|
||||||
|
|
||||||
|
def lookup_symbol(self, name):
|
||||||
|
if name in self.symbols:
|
||||||
|
return self.symbols[name]
|
||||||
|
if name in self.global_symbol_table:
|
||||||
|
return self.global_symbol_table[name]
|
||||||
|
raise LinkerException('Symbol {} not found!'.format(name), (self,))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{}[{}](base=0x{:08X}, size={}bytes, sections={}, run_ptr=0x{:08X})'.format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.name,
|
||||||
|
self.base_addr,
|
||||||
|
self.size,
|
||||||
|
" ".join(self.sections_by_name.keys()),
|
||||||
|
self.run_ptr
|
||||||
|
)
|
||||||
|
|
||||||
|
def has_symb(self, arg):
|
||||||
|
return arg in self.symbols or arg in self.global_symbol_table
|
@ -0,0 +1,193 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
This file holds the parser that parses the tokenizer output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .helpers import parse_numeric_argument, int_to_bytes
|
||||||
|
from .Executable import Executable, InstructionMemorySection, MemorySection, MemoryFlags
|
||||||
|
from .Exceptions import *
|
||||||
|
|
||||||
|
from .Tokenizer import RiscVTokenizer, RiscVInstructionToken, RiscVSymbolToken, RiscVPseudoOpToken
|
||||||
|
|
||||||
|
from typing import Dict, Tuple, List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutableParser:
|
||||||
|
"""
|
||||||
|
Parses output form the RiscVTokenizer
|
||||||
|
"""
|
||||||
|
tokenizer: 'RiscVTokenizer'
|
||||||
|
|
||||||
|
def __init__(self, tokenizer: 'RiscVTokenizer'):
|
||||||
|
self.instructions: List[RiscVInstructionToken] = list()
|
||||||
|
self.symbols: Dict[str, Tuple[str, int]] = dict()
|
||||||
|
self.sections: Dict[str, MemorySection] = dict()
|
||||||
|
self.tokenizer = tokenizer
|
||||||
|
self.active_section: Optional[str] = None
|
||||||
|
self.implicit_sections = False
|
||||||
|
self.globals: List[str] = list()
|
||||||
|
|
||||||
|
def parse(self) -> Executable:
|
||||||
|
"""
|
||||||
|
parse tokenizer output into an executable
|
||||||
|
:return: the parsed executable
|
||||||
|
:raise ParseException: Raises a ParseException when invalid input is read
|
||||||
|
"""
|
||||||
|
for token in self.tokenizer.tokens:
|
||||||
|
if isinstance(token, RiscVInstructionToken):
|
||||||
|
self.parse_instruction(token)
|
||||||
|
elif isinstance(token, RiscVSymbolToken):
|
||||||
|
self.handle_symbol(token)
|
||||||
|
elif isinstance(token, RiscVPseudoOpToken):
|
||||||
|
self.handle_pseudo_op(token)
|
||||||
|
return self._get_execuable()
|
||||||
|
|
||||||
|
def _get_execuable(self) -> Executable:
|
||||||
|
start_ptr = ('text', 0)
|
||||||
|
if '_start' in self.symbols:
|
||||||
|
start_ptr = self.symbols['_start']
|
||||||
|
elif 'main' in self.symbols:
|
||||||
|
start_ptr = self.symbols['main']
|
||||||
|
return Executable(start_ptr, self.sections, self.symbols, self.globals, self.tokenizer.name)
|
||||||
|
|
||||||
|
def parse_instruction(self, ins: 'RiscVInstructionToken') -> None:
|
||||||
|
"""
|
||||||
|
parses an Instruction token
|
||||||
|
:param ins: the instruction token
|
||||||
|
"""
|
||||||
|
if self.active_section is None:
|
||||||
|
self.op_text()
|
||||||
|
self.implicit_sections = True
|
||||||
|
|
||||||
|
ASSERT_EQ(self.active_section, 'text')
|
||||||
|
sec = self._curr_sec()
|
||||||
|
if isinstance(sec, InstructionMemorySection):
|
||||||
|
sec.add_insn(ins)
|
||||||
|
else:
|
||||||
|
raise ParseException("SHOULD NOT BE REACHED")
|
||||||
|
|
||||||
|
def handle_symbol(self, token: 'RiscVSymbolToken'):
|
||||||
|
"""
|
||||||
|
Handle a symbol token (such as 'main:')
|
||||||
|
:param token: the symbol token
|
||||||
|
"""
|
||||||
|
ASSERT_NOT_IN(token.name, self.symbols)
|
||||||
|
ASSERT_NOT_NULL(self.active_section)
|
||||||
|
sec_pos = self._curr_sec().size
|
||||||
|
self.symbols[token.name] = (self.active_section, sec_pos)
|
||||||
|
|
||||||
|
def handle_pseudo_op(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
Handle a pseudo op token (such as '.word 0xffaabbcc')
|
||||||
|
:param op: the peseudo-op token
|
||||||
|
"""
|
||||||
|
name = 'op_' + op.name
|
||||||
|
if hasattr(self, name):
|
||||||
|
getattr(self, name)(op)
|
||||||
|
else:
|
||||||
|
raise ParseException("Unknown pseudo op: {}".format(op), (op,))
|
||||||
|
|
||||||
|
## Pseudo op implementations:
|
||||||
|
def op_section(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .section token
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
name = op.args[0][1:]
|
||||||
|
ASSERT_IN(name, ('data', 'rodata', 'text'))
|
||||||
|
getattr(self, 'op_' + name)(op)
|
||||||
|
|
||||||
|
def op_text(self, op: 'RiscVPseudoOpToken' = None):
|
||||||
|
"""
|
||||||
|
handles a .text token
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
self._set_sec('text', MemoryFlags(read_only=True, executable=True), cls=InstructionMemorySection)
|
||||||
|
|
||||||
|
def op_data(self, op: 'RiscVPseudoOpToken' = None):
|
||||||
|
"""
|
||||||
|
handles a .data token
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
self._set_sec('data', MemoryFlags(read_only=False, executable=False))
|
||||||
|
|
||||||
|
def op_rodata(self, op: 'RiscVPseudoOpToken' = None):
|
||||||
|
"""
|
||||||
|
handles a .rodata token
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
self._set_sec('rodata', MemoryFlags(read_only=True, executable=False))
|
||||||
|
|
||||||
|
def op_space(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .space token. Inserts empty space into the current (data or rodata) section
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_IN(self.active_section, ('data', 'rodata'))
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
size = parse_numeric_argument(op.args[0])
|
||||||
|
self._curr_sec().add(bytearray(size))
|
||||||
|
|
||||||
|
def op_ascii(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .ascii token. Inserts ascii encoded text into the currrent data section
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_IN(self.active_section, ('data', 'rodata'))
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
str = op.args[0][1:-1].encode('ascii').decode('unicode_escape')
|
||||||
|
self._curr_sec().add(bytearray(str, 'ascii'))
|
||||||
|
|
||||||
|
def op_asciiz(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .ascii token. Inserts nullterminated ascii encoded text into the currrent data section
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_IN(self.active_section, ('data', 'rodata'))
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
str = op.args[0][1:-1].encode('ascii').decode('unicode_escape')
|
||||||
|
self._curr_sec().add(bytearray(str + '\0', 'ascii'))
|
||||||
|
|
||||||
|
def op_global(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .global token. Marks the token as global
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
name = op.args[0]
|
||||||
|
self.globals.append(name)
|
||||||
|
|
||||||
|
def op_set(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles a .set name, val token. Sets the symbol name to val
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
ASSERT_LEN(op.args, 2)
|
||||||
|
name = op.args[0]
|
||||||
|
val = parse_numeric_argument(op.args[1])
|
||||||
|
self.symbols[name] = ('_static_', val)
|
||||||
|
|
||||||
|
def op_align(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
"""
|
||||||
|
handles an align token. Currently a nop (just not implemented fully yet, as linker handles most alignement tasks)
|
||||||
|
:param op: The token
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def op_word(self, op: 'RiscVPseudoOpToken'):
|
||||||
|
ASSERT_LEN(op.args, 1)
|
||||||
|
val = parse_numeric_argument(op.args[0])
|
||||||
|
self._curr_sec().add(int_to_bytes(val, 4))
|
||||||
|
|
||||||
|
## Section handler code
|
||||||
|
def _set_sec(self, name: str, flags: MemoryFlags, cls=MemorySection):
|
||||||
|
if name not in self.sections:
|
||||||
|
self.sections[name] = cls(name, flags)
|
||||||
|
self.active_section = name
|
||||||
|
|
||||||
|
def _curr_sec(self):
|
||||||
|
return self.sections[self.active_section]
|
@ -1,30 +1,22 @@
|
|||||||
from abc import ABC
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress
|
|
||||||
|
|
||||||
|
class IOModule(ABC):
|
||||||
|
addr: int
|
||||||
|
size: int
|
||||||
|
|
||||||
class IOModule(MemorySection, ABC):
|
def __init__(self, addr: int, size: int):
|
||||||
def __init__(
|
self.addr = addr
|
||||||
self,
|
self.size = size
|
||||||
name: str,
|
|
||||||
flags: MemoryFlags,
|
|
||||||
size: int,
|
|
||||||
owner: str = "system",
|
|
||||||
base: int = 0,
|
|
||||||
):
|
|
||||||
super(IOModule, self).__init__(name, flags, size, base, owner, None)
|
|
||||||
|
|
||||||
def contains(self, addr, size: int = 0):
|
@abstractmethod
|
||||||
return (
|
def read(self, addr: int, size: int):
|
||||||
self.base <= addr < self.base + self.size
|
pass
|
||||||
and self.base <= addr + size <= self.base + self.size
|
|
||||||
)
|
|
||||||
|
|
||||||
def dump(self, *args, **kwargs):
|
@abstractmethod
|
||||||
print(self)
|
def write(self, addr: int, data: bytearray, size: int):
|
||||||
|
pass
|
||||||
|
|
||||||
def __repr__(self):
|
def contains(self, addr, size: int = 0):
|
||||||
return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format(
|
return self.addr <= addr < self.addr + self.size and \
|
||||||
self.__class__.__name__, self.name, self.base, self.size, self.flags
|
self.addr <= addr + size <= self.addr + self.size
|
||||||
)
|
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .Config import RunConfig
|
||||||
|
from .helpers import *
|
||||||
|
from collections import defaultdict
|
||||||
|
from .Exceptions import InvalidRegisterException
|
||||||
|
|
||||||
|
class Registers:
|
||||||
|
"""
|
||||||
|
Represents a bunch of registers
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, conf: RunConfig):
|
||||||
|
"""
|
||||||
|
Initialize the register configuration, respecting the RunConfig conf
|
||||||
|
:param conf: The RunConfig
|
||||||
|
"""
|
||||||
|
self.vals = defaultdict(lambda: 0)
|
||||||
|
self.last_set = None
|
||||||
|
self.last_read = None
|
||||||
|
self.conf = conf
|
||||||
|
|
||||||
|
def dump(self, full=False):
|
||||||
|
"""
|
||||||
|
Dump all registers to stdout
|
||||||
|
:param full: If True, floating point registers are dumped too
|
||||||
|
"""
|
||||||
|
named_regs = [self._reg_repr(reg) for reg in Registers.named_registers()]
|
||||||
|
|
||||||
|
lines = [[] for i in range(12)]
|
||||||
|
if not full:
|
||||||
|
regs = [('a', 8), ('s', 12), ('t', 7)]
|
||||||
|
else:
|
||||||
|
regs = [
|
||||||
|
('a', 8),
|
||||||
|
('s', 12),
|
||||||
|
('t', 7),
|
||||||
|
('ft', 8),
|
||||||
|
('fa', 8),
|
||||||
|
('fs', 12),
|
||||||
|
]
|
||||||
|
for name, s in regs:
|
||||||
|
for i in range(12):
|
||||||
|
if i >= s:
|
||||||
|
lines[i].append(" " * 15)
|
||||||
|
else:
|
||||||
|
reg = '{}{}'.format(name, i)
|
||||||
|
lines[i].append(self._reg_repr(reg))
|
||||||
|
|
||||||
|
print("Registers[{},{}](".format(
|
||||||
|
FMT_ORANGE + FMT_UNDERLINE + 'read' + FMT_NONE,
|
||||||
|
FMT_ORANGE + FMT_BOLD + 'written' + FMT_NONE
|
||||||
|
))
|
||||||
|
if not full:
|
||||||
|
print("\t" + " ".join(named_regs[0:3]))
|
||||||
|
print("\t" + " ".join(named_regs[3:]))
|
||||||
|
print("\t" + "--------------- " * 3)
|
||||||
|
else:
|
||||||
|
print("\t" + " ".join(named_regs))
|
||||||
|
print("\t" + "--------------- " * 6)
|
||||||
|
for line in lines:
|
||||||
|
print("\t" + " ".join(line))
|
||||||
|
print(")")
|
||||||
|
|
||||||
|
def dump_reg_a(self):
|
||||||
|
"""
|
||||||
|
Dump the a registers
|
||||||
|
"""
|
||||||
|
print("Registers[a]:" + " ".join(self._reg_repr('a{}'.format(i)) for i in range(8)))
|
||||||
|
|
||||||
|
def _reg_repr(self, reg):
|
||||||
|
txt = '{:4}=0x{:08X}'.format(reg, self.get(reg, False))
|
||||||
|
if reg == 'fp':
|
||||||
|
reg = 's0'
|
||||||
|
if reg == self.last_set:
|
||||||
|
return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE
|
||||||
|
if reg == self.last_read:
|
||||||
|
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
|
||||||
|
if reg == 'zero':
|
||||||
|
return txt
|
||||||
|
if self.get(reg, False) == 0 and reg not in Registers.named_registers():
|
||||||
|
return FMT_GRAY + txt + FMT_NONE
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def set(self, reg, val, mark_set=True) -> bool:
|
||||||
|
"""
|
||||||
|
Set a register content to val
|
||||||
|
:param reg: The register to set
|
||||||
|
:param val: The new value
|
||||||
|
:param mark_set: If True, marks this register as "last accessed" (only used internally)
|
||||||
|
:return: If the operation was successful
|
||||||
|
"""
|
||||||
|
if reg == 'zero':
|
||||||
|
return False
|
||||||
|
#if reg not in Registers.all_registers():
|
||||||
|
# raise InvalidRegisterException(reg)
|
||||||
|
# replace fp register with s1, as these are the same register
|
||||||
|
if reg == 'fp':
|
||||||
|
reg = 's1'
|
||||||
|
if mark_set:
|
||||||
|
self.last_set = reg
|
||||||
|
# check 32 bit signed bounds
|
||||||
|
if val < -2147483648:
|
||||||
|
val = -2147483648
|
||||||
|
elif val > 2147483647:
|
||||||
|
val = 2147483647
|
||||||
|
self.vals[reg] = val
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get(self, reg, mark_read=True):
|
||||||
|
"""
|
||||||
|
Retuns the contents of register reg
|
||||||
|
:param reg: The register name
|
||||||
|
:param mark_read: If the register should be markes as "last read" (only used internally)
|
||||||
|
:return: The contents of register reg
|
||||||
|
"""
|
||||||
|
#if reg not in Registers.all_registers():
|
||||||
|
# raise InvalidRegisterException(reg)
|
||||||
|
if reg == 'fp':
|
||||||
|
reg = 's0'
|
||||||
|
if mark_read:
|
||||||
|
self.last_read = reg
|
||||||
|
return self.vals[reg]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def all_registers():
|
||||||
|
"""
|
||||||
|
Return a list of all valid registers
|
||||||
|
:return: The list
|
||||||
|
"""
|
||||||
|
return ['zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp',
|
||||||
|
't0', 't1', 't2', 't3', 't4', 't5', 't6',
|
||||||
|
's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
|
||||||
|
'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
|
||||||
|
'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7',
|
||||||
|
'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11',
|
||||||
|
'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def named_registers():
|
||||||
|
"""
|
||||||
|
Return all named registers
|
||||||
|
:return: The list
|
||||||
|
"""
|
||||||
|
return ['zero', 'ra', 'sp', 'gp', 'tp', 'fp']
|
@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, IO
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .helpers import *
|
||||||
|
|
||||||
|
import riscemu
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from . import CPU
|
||||||
|
|
||||||
|
SYSCALLS = {
|
||||||
|
63: 'read',
|
||||||
|
64: 'write',
|
||||||
|
93: 'exit',
|
||||||
|
1024: 'open',
|
||||||
|
1025: 'close',
|
||||||
|
}
|
||||||
|
"""All available syscalls (mapped id->name)"""
|
||||||
|
|
||||||
|
OPEN_MODES = {
|
||||||
|
0: 'rb',
|
||||||
|
1: 'wb',
|
||||||
|
2: 'r+b',
|
||||||
|
3: 'x',
|
||||||
|
4: 'ab',
|
||||||
|
}
|
||||||
|
"""All available file open modes"""
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Syscall:
|
||||||
|
"""
|
||||||
|
Represents a syscall
|
||||||
|
"""
|
||||||
|
id: int
|
||||||
|
"""The syscall number (e.g. 64 - write)"""
|
||||||
|
cpu: 'riscemu.CPU'
|
||||||
|
"""The CPU object that created the syscall"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return SYSCALLS.get(self.id, "unknown")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Syscall(id={}, name={})".format(
|
||||||
|
self.id, self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
def ret(self, code):
|
||||||
|
self.cpu.regs.set('a0', code)
|
||||||
|
|
||||||
|
|
||||||
|
def get_syscall_symbols():
|
||||||
|
"""
|
||||||
|
Generate global syscall symbols (such as SCALL_READ, SCALL_EXIT etc)
|
||||||
|
|
||||||
|
:return: dictionary of all syscall symbols (SCALL_<name> -> id)
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
('SCALL_' + name.upper()): num for num, name in SYSCALLS.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SyscallInterface:
|
||||||
|
"""
|
||||||
|
Handles syscalls
|
||||||
|
"""
|
||||||
|
open_files: Dict[int, IO]
|
||||||
|
next_open_handle: int
|
||||||
|
|
||||||
|
def handle_syscall(self, scall: Syscall):
|
||||||
|
self.next_open_handle = 3
|
||||||
|
self.open_files = {
|
||||||
|
0: sys.stdin,
|
||||||
|
1: sys.stdout,
|
||||||
|
2: sys.stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
if getattr(self, scall.name):
|
||||||
|
getattr(self, scall.name)(scall)
|
||||||
|
else:
|
||||||
|
raise InvalidSyscallException(scall)
|
||||||
|
|
||||||
|
def read(self, scall: Syscall):
|
||||||
|
"""
|
||||||
|
read syscall (63): read from file no a0, into addr a1, at most a2 bytes
|
||||||
|
on return a0 will be the number of read bytes or -1 if an error occured
|
||||||
|
"""
|
||||||
|
fileno = scall.cpu.regs.get('a0')
|
||||||
|
addr = scall.cpu.regs.get('a1')
|
||||||
|
size = scall.cpu.regs.get('a2')
|
||||||
|
if fileno not in self.open_files:
|
||||||
|
scall.cpu.regs.set('a0', -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
chars = self.open_files[fileno].readline(size)
|
||||||
|
try:
|
||||||
|
data = bytearray(chars, 'ascii')
|
||||||
|
scall.cpu.mmu.write(addr, len(data), data)
|
||||||
|
return scall.ret(len(data))
|
||||||
|
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
print(FMT_SYSCALL + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
def write(self, scall: Syscall):
|
||||||
|
"""
|
||||||
|
write syscall (64): write a2 bytes from addr a1 into fileno a0
|
||||||
|
on return a0 will hold the number of bytes written or -1 if an error occured
|
||||||
|
"""
|
||||||
|
fileno = scall.cpu.regs.get('a0')
|
||||||
|
addr = scall.cpu.regs.get('a1')
|
||||||
|
size = scall.cpu.regs.get('a2')
|
||||||
|
if fileno not in self.open_files:
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
data = scall.cpu.mmu.read(addr, size)
|
||||||
|
|
||||||
|
if not isinstance(data, bytearray):
|
||||||
|
print(FMT_SYSCALL + '[Syscall] write: writing from .text region not supported.' + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
self.open_files[fileno].write(data.decode('ascii'))
|
||||||
|
return scall.ret(size)
|
||||||
|
|
||||||
|
def open(self, scall: Syscall):
|
||||||
|
"""
|
||||||
|
open syscall (1024): read path of a2 bytes from addr a1, in mode a0
|
||||||
|
returns the file no in a0
|
||||||
|
|
||||||
|
modes:
|
||||||
|
- 0: read
|
||||||
|
- 1: write (truncate)
|
||||||
|
- 2: read/write (no truncate)
|
||||||
|
- 3: only create
|
||||||
|
- 4: append
|
||||||
|
|
||||||
|
Requires running with flag scall-fs
|
||||||
|
"""
|
||||||
|
if not scall.cpu.conf.scall_fs:
|
||||||
|
print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
mode = scall.cpu.regs.get('a0')
|
||||||
|
addr = scall.cpu.regs.get('a1')
|
||||||
|
size = scall.cpu.regs.get('a2')
|
||||||
|
|
||||||
|
mode_st = OPEN_MODES.get(mode, )
|
||||||
|
if mode_st == -1:
|
||||||
|
print(FMT_SYSCALL + '[Syscall] open: unknown opening mode {}!'.format(mode) + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
path = scall.cpu.mmu.read(addr, size).decode('ascii')
|
||||||
|
|
||||||
|
fileno = self.next_open_handle
|
||||||
|
self.next_open_handle += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.open_files[fileno] = open(path, mode_st)
|
||||||
|
except OSError as err:
|
||||||
|
print(FMT_SYSCALL + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE)
|
||||||
|
return scall.ret(fileno)
|
||||||
|
|
||||||
|
def close(self, scall: Syscall):
|
||||||
|
"""
|
||||||
|
close syscall (1025): closes file no a0
|
||||||
|
|
||||||
|
return -1 if an error was encountered, otherwise returns 0
|
||||||
|
"""
|
||||||
|
fileno = scall.cpu.regs.get('a0')
|
||||||
|
if fileno not in self.open_files:
|
||||||
|
print(FMT_SYSCALL + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE)
|
||||||
|
return scall.ret(-1)
|
||||||
|
|
||||||
|
self.open_files[fileno].close()
|
||||||
|
print(FMT_SYSCALL + '[Syscall] close: closed fd {}!'.format(fileno) + FMT_NONE)
|
||||||
|
del self.open_files[fileno]
|
||||||
|
return scall.ret(0)
|
||||||
|
|
||||||
|
def exit(self, scall: Syscall):
|
||||||
|
"""
|
||||||
|
Exit syscall. Exits the system with status code a0
|
||||||
|
"""
|
||||||
|
scall.cpu.exit = True
|
||||||
|
scall.cpu.exit_code = scall.cpu.regs.get('a0')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}(\n\tfiles={}\n)".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.open_files
|
||||||
|
)
|
@ -0,0 +1,320 @@
|
|||||||
|
"""
|
||||||
|
RiscEmu (c) 2021 Anton Lydike
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .Exceptions import ParseException
|
||||||
|
|
||||||
|
PSEUDO_OPS = [
|
||||||
|
'.asciiz',
|
||||||
|
'.double',
|
||||||
|
'.extern',
|
||||||
|
'.global',
|
||||||
|
'.align',
|
||||||
|
'.float',
|
||||||
|
'.kdata',
|
||||||
|
'.ktext',
|
||||||
|
'.space',
|
||||||
|
'.ascii',
|
||||||
|
'.byte',
|
||||||
|
'.data',
|
||||||
|
'.half',
|
||||||
|
'.text',
|
||||||
|
'.word',
|
||||||
|
'.set',
|
||||||
|
]
|
||||||
|
|
||||||
|
COMMENT_START = ["#", ";"]
|
||||||
|
|
||||||
|
REG_VALID_SYMBOL_LABEL = re.compile(r'^([A-z_.][A-z_0-9.]*[A-z_0-9]|[A-z_]):')
|
||||||
|
|
||||||
|
REG_WHITESPACE_UNTIL_NEWLINE = re.compile(r'^(\s*)\n')
|
||||||
|
|
||||||
|
REG_WHITESPACE = re.compile(r'^\s*')
|
||||||
|
|
||||||
|
REG_NONWHITESPACE = re.compile(r'^[^\s]*')
|
||||||
|
|
||||||
|
REG_UNTIL_NEWLINE = re.compile(r'^[^\n]*')
|
||||||
|
|
||||||
|
REG_WHITESPACE_NO_LINEBREAK = re.compile(r'^[ \t]*')
|
||||||
|
|
||||||
|
REG_VALID_ARGUMENT = re.compile(
|
||||||
|
r'^([+-]?(0x[0-9A-f]+|[0-9]+)|[A-z_.][A-z0-9_.]*[A-z_0-9]|[A-z_])(\(([A-z_.][A-z_0-9.]*[A-z_0-9]|[A-z_])\))?'
|
||||||
|
)
|
||||||
|
|
||||||
|
REG_ARG_SPLIT = re.compile(r'^,[ \t]*')
|
||||||
|
|
||||||
|
|
||||||
|
def split_accepting_quotes(string, at=REG_ARG_SPLIT, quotes=('"', "'")):
|
||||||
|
pos = 0
|
||||||
|
last_piece = 0
|
||||||
|
pieces = []
|
||||||
|
in_quotes = False
|
||||||
|
if string is None:
|
||||||
|
return pieces
|
||||||
|
while pos < len(string):
|
||||||
|
match = at.match(string[pos:])
|
||||||
|
if match is not None:
|
||||||
|
if not in_quotes:
|
||||||
|
pieces.append(string[last_piece:pos])
|
||||||
|
pos += len(match.group(0))
|
||||||
|
last_piece = pos
|
||||||
|
else:
|
||||||
|
pos += len(match.group(0))
|
||||||
|
elif string[pos] in quotes:
|
||||||
|
in_quotes = not in_quotes
|
||||||
|
pos += 1
|
||||||
|
elif string[pos] in COMMENT_START and not in_quotes: # entering comment
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
pos += 1
|
||||||
|
if in_quotes:
|
||||||
|
print("[Tokenizer.split] unbalanced quotes in \"{}\"!".format(string))
|
||||||
|
pieces.append(string[last_piece:pos])
|
||||||
|
return pieces
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVInput:
|
||||||
|
"""
|
||||||
|
Represents an Assembly file
|
||||||
|
"""
|
||||||
|
def __init__(self, content: str, name: str):
|
||||||
|
self.content = content
|
||||||
|
self.pos = 0
|
||||||
|
self.len = len(content)
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_file(src: str):
|
||||||
|
with open(src, 'r') as f:
|
||||||
|
return RiscVInput(f.read(), src)
|
||||||
|
|
||||||
|
def peek(self, offset: int = 0, size: int = 1, regex: re.Pattern = None, text: str = None, regex_group: int = 0):
|
||||||
|
at = self.pos + offset
|
||||||
|
|
||||||
|
if regex:
|
||||||
|
if not isinstance(regex, re.Pattern):
|
||||||
|
print("uncompiled regex passed to peek!")
|
||||||
|
regex = re.compile(regex)
|
||||||
|
match = regex.match(self.content[at:])
|
||||||
|
if match is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if regex_group != 0 and not match.group(0).startswith(match.group(regex_group)):
|
||||||
|
print("Cannot peek regex group that does not start at match start!")
|
||||||
|
return None
|
||||||
|
return match.group(regex_group)
|
||||||
|
if text:
|
||||||
|
if self.content[at:].startswith(text):
|
||||||
|
return self.content[at:at + len(text)]
|
||||||
|
return False
|
||||||
|
return self.content[at:at + size]
|
||||||
|
|
||||||
|
def peek_one_of(self, options: List[str]):
|
||||||
|
longest_peek = 0
|
||||||
|
ret = False
|
||||||
|
for text in options:
|
||||||
|
if self.peek(text=text):
|
||||||
|
if len(text) > longest_peek:
|
||||||
|
longest_peek = len(text)
|
||||||
|
ret = text
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def consume(self, size: int = 1, regex: re.Pattern = None, text: str = None, regex_group: int = 0):
|
||||||
|
at = self.pos
|
||||||
|
|
||||||
|
if regex:
|
||||||
|
if not isinstance(regex, re.Pattern):
|
||||||
|
print("uncompiled regex passed to peek!")
|
||||||
|
regex = re.compile(regex)
|
||||||
|
match = regex.match(self.content[at:])
|
||||||
|
if match is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if regex_group != 0 and not match.group(0).startswith(match.group(regex_group)):
|
||||||
|
print("Cannot consume regex group that does not start at match start!")
|
||||||
|
return None
|
||||||
|
self.pos += len(match.group(regex_group))
|
||||||
|
return match.group(regex_group)
|
||||||
|
|
||||||
|
if text:
|
||||||
|
if self.content[at:].startswith(text):
|
||||||
|
self.pos += len(text)
|
||||||
|
return text
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.pos += size
|
||||||
|
return self.content[at:at + size]
|
||||||
|
|
||||||
|
def consume_one_of(self, options: List[str]):
|
||||||
|
longest_peek = 0
|
||||||
|
ret = False
|
||||||
|
for text in options:
|
||||||
|
if self.peek(text=text):
|
||||||
|
if len(text) > longest_peek:
|
||||||
|
longest_peek = len(text)
|
||||||
|
ret = text
|
||||||
|
self.consume(text=ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def seek_newline(self):
|
||||||
|
return self.consume(regex=REG_WHITESPACE_UNTIL_NEWLINE, regex_group=1)
|
||||||
|
|
||||||
|
def consume_whitespace(self, linebreak=True):
|
||||||
|
if linebreak:
|
||||||
|
return self.consume(regex=REG_WHITESPACE)
|
||||||
|
return self.consume(regex=REG_WHITESPACE_NO_LINEBREAK)
|
||||||
|
|
||||||
|
def has_next(self):
|
||||||
|
return self.pos < self.len
|
||||||
|
|
||||||
|
def context(self, size: int = 5):
|
||||||
|
"""
|
||||||
|
returns a context string:
|
||||||
|
<local input before pos>|<local input after pos>
|
||||||
|
"""
|
||||||
|
start = max(self.pos - size, 0)
|
||||||
|
end = min(self.pos + size, self.len - 1)
|
||||||
|
|
||||||
|
return self.content[start:self.pos] + '|' + self.content[self.pos:end]
|
||||||
|
|
||||||
|
|
||||||
|
class TokenType(IntEnum):
|
||||||
|
SYMBOL = 0
|
||||||
|
INSTRUCTION = 1
|
||||||
|
PSEUDO_OP = 2
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVToken:
|
||||||
|
type: TokenType
|
||||||
|
|
||||||
|
def __init__(self, t_type: TokenType):
|
||||||
|
self.type = t_type
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}[{}]({})".format(self.__class__.__name__, self.type, self.text())
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
"""
|
||||||
|
create text representation of instruction
|
||||||
|
"""
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVInstructionToken(RiscVToken):
|
||||||
|
def __init__(self, name, args):
|
||||||
|
super().__init__(TokenType.INSTRUCTION)
|
||||||
|
self.instruction = name
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
if len(self.args) == 0:
|
||||||
|
return self.instruction
|
||||||
|
if len(self.args) == 1:
|
||||||
|
return "{} {}".format(self.instruction, self.args[0])
|
||||||
|
if len(self.args) == 2:
|
||||||
|
return "{} {}, {}".format(self.instruction, *self.args)
|
||||||
|
return "{} {}, {}, {}".format(self.instruction, *self.args)
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVSymbolToken(RiscVToken):
|
||||||
|
def __init__(self, name):
|
||||||
|
super().__init__(TokenType.SYMBOL)
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVPseudoOpToken(RiscVToken):
|
||||||
|
def __init__(self, name, args):
|
||||||
|
super().__init__(TokenType.PSEUDO_OP)
|
||||||
|
self.name = name
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return "{} {}".format(self.name, self.args)
|
||||||
|
|
||||||
|
|
||||||
|
class RiscVTokenizer:
|
||||||
|
"""
|
||||||
|
A tokenizer for the RISC-V syntax of a given CPU
|
||||||
|
"""
|
||||||
|
def __init__(self, input_assembly: RiscVInput, instructions: List[str]):
|
||||||
|
self.input = input_assembly
|
||||||
|
self.tokens: List[RiscVToken] = []
|
||||||
|
self.name = input_assembly.name
|
||||||
|
self.instructions = instructions
|
||||||
|
|
||||||
|
def tokenize(self):
|
||||||
|
while self.input.has_next():
|
||||||
|
# remove leading whitespaces, place cursor at text start
|
||||||
|
self.input.consume_whitespace()
|
||||||
|
|
||||||
|
# check if we have a pseudo op
|
||||||
|
if self.input.peek_one_of(PSEUDO_OPS):
|
||||||
|
self.parse_pseudo_op()
|
||||||
|
|
||||||
|
# check if we have a symbol (like main:)
|
||||||
|
elif self.input.peek(regex=REG_VALID_SYMBOL_LABEL):
|
||||||
|
self.parse_symbol()
|
||||||
|
|
||||||
|
# comment
|
||||||
|
elif self.input.peek() in COMMENT_START:
|
||||||
|
self.parse_comment()
|
||||||
|
|
||||||
|
# must be instruction
|
||||||
|
elif self.input.peek_one_of(self.instructions):
|
||||||
|
self.parse_instruction()
|
||||||
|
else:
|
||||||
|
token = self.input.peek(size=5)
|
||||||
|
raise ParseException("Unknown token around {} at: {}".format(repr(token), repr(self.input.context())))
|
||||||
|
self.input.consume_whitespace()
|
||||||
|
|
||||||
|
def parse_pseudo_op(self):
|
||||||
|
name = self.input.consume_one_of(PSEUDO_OPS)
|
||||||
|
self.input.consume_whitespace(linebreak=False)
|
||||||
|
|
||||||
|
arg_str = self.input.consume(regex=REG_UNTIL_NEWLINE)
|
||||||
|
if not arg_str:
|
||||||
|
args = []
|
||||||
|
else:
|
||||||
|
args = split_accepting_quotes(arg_str)
|
||||||
|
|
||||||
|
self.tokens.append(RiscVPseudoOpToken(name[1:], args))
|
||||||
|
|
||||||
|
def parse_symbol(self):
|
||||||
|
name = self.input.consume(regex=REG_VALID_SYMBOL_LABEL)
|
||||||
|
self.tokens.append(RiscVSymbolToken(name[:-1]))
|
||||||
|
if not self.input.consume_whitespace():
|
||||||
|
print("[Tokenizer] symbol declaration should always be followed by whitespace (at {})!".format(
|
||||||
|
self.input.context()))
|
||||||
|
|
||||||
|
def parse_instruction(self):
|
||||||
|
ins = self.input.consume_one_of(self.instructions)
|
||||||
|
args = []
|
||||||
|
self.input.consume_whitespace(linebreak=False)
|
||||||
|
while self.input.peek(regex=REG_VALID_ARGUMENT) and len(args) < 3:
|
||||||
|
arg = self.input.consume(regex=REG_VALID_ARGUMENT)
|
||||||
|
args.append(arg)
|
||||||
|
if self.input.peek(text=','):
|
||||||
|
self.input.consume(text=',')
|
||||||
|
self.input.consume_whitespace(linebreak=False)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
self.tokens.append(RiscVInstructionToken(ins, args))
|
||||||
|
|
||||||
|
def parse_comment(self):
|
||||||
|
# just consume the rest
|
||||||
|
self.input.consume(regex=REG_UNTIL_NEWLINE)
|
@ -1,256 +0,0 @@
|
|||||||
from enum import Enum, auto
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional, Tuple, Union
|
|
||||||
|
|
||||||
from .colors import FMT_PARSE, FMT_NONE
|
|
||||||
from riscemu.types.exceptions import ParseException, ASSERT_LEN
|
|
||||||
from .helpers import parse_numeric_argument, align_addr, get_section_base_name
|
|
||||||
from .tokenizer import Token
|
|
||||||
from .types import (
|
|
||||||
Program,
|
|
||||||
T_RelativeAddress,
|
|
||||||
InstructionContext,
|
|
||||||
Instruction,
|
|
||||||
BinaryDataMemorySection,
|
|
||||||
InstructionMemorySection,
|
|
||||||
Int32,
|
|
||||||
)
|
|
||||||
|
|
||||||
INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini")
|
|
||||||
"""
|
|
||||||
A tuple containing all section names which contain executable code (instead of data)
|
|
||||||
|
|
||||||
The first segment of each segment (first segment of ".text.main" is ".text") is checked
|
|
||||||
against this list to determine the type of it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class MemorySectionType(Enum):
|
|
||||||
Data = auto()
|
|
||||||
Instructions = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class CurrentSection:
|
|
||||||
name: str
|
|
||||||
data: Union[List[Instruction], bytearray]
|
|
||||||
type: MemorySectionType
|
|
||||||
base: int
|
|
||||||
|
|
||||||
def __init__(self, name: str, type: MemorySectionType, base: int = 0):
|
|
||||||
self.name = name
|
|
||||||
self.type = type
|
|
||||||
self.base = base
|
|
||||||
if self.type == MemorySectionType.Data:
|
|
||||||
self.data = bytearray()
|
|
||||||
elif self.type == MemorySectionType.Instructions:
|
|
||||||
self.data = list()
|
|
||||||
else:
|
|
||||||
raise ParseException("Unknown section type: {}".format(type))
|
|
||||||
|
|
||||||
def current_address(self) -> T_RelativeAddress:
|
|
||||||
if self.type == MemorySectionType.Data:
|
|
||||||
return len(self.data) + self.base
|
|
||||||
return len(self.data) * 4 + self.base
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}(name={},data={},type={})".format(
|
|
||||||
self.__class__.__name__, self.name, self.data, self.type.name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ParseContext:
|
|
||||||
section: Optional[CurrentSection]
|
|
||||||
context: InstructionContext
|
|
||||||
program: Program
|
|
||||||
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.program = Program(name)
|
|
||||||
self.context = self.program.context
|
|
||||||
self.section = None
|
|
||||||
|
|
||||||
def finalize(self) -> Program:
|
|
||||||
self._finalize_section()
|
|
||||||
return self.program
|
|
||||||
|
|
||||||
def _finalize_section(self):
|
|
||||||
if self.section is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.section.type == MemorySectionType.Data:
|
|
||||||
section = BinaryDataMemorySection(
|
|
||||||
self.section.data,
|
|
||||||
self.section.name,
|
|
||||||
self.context,
|
|
||||||
self.program.name,
|
|
||||||
self.section.base,
|
|
||||||
)
|
|
||||||
self.program.add_section(section)
|
|
||||||
elif self.section.type == MemorySectionType.Instructions:
|
|
||||||
section = InstructionMemorySection(
|
|
||||||
self.section.data,
|
|
||||||
self.section.name,
|
|
||||||
self.context,
|
|
||||||
self.program.name,
|
|
||||||
self.section.base,
|
|
||||||
)
|
|
||||||
self.program.add_section(section)
|
|
||||||
|
|
||||||
self.section = None
|
|
||||||
|
|
||||||
def new_section(self, name: str, type: MemorySectionType, alignment: int = 4):
|
|
||||||
base = align_addr(self.current_address(), alignment)
|
|
||||||
|
|
||||||
self._finalize_section()
|
|
||||||
self.section = CurrentSection(name, type, base)
|
|
||||||
|
|
||||||
def add_label(
|
|
||||||
self, name: str, value: int, is_global: bool = False, is_relative: bool = False
|
|
||||||
):
|
|
||||||
self.context.labels[name] = value
|
|
||||||
if is_global:
|
|
||||||
self.program.global_labels.add(name)
|
|
||||||
if is_relative:
|
|
||||||
self.program.relative_labels.add(name)
|
|
||||||
|
|
||||||
def current_address(self):
|
|
||||||
if self.section:
|
|
||||||
return self.section.current_address()
|
|
||||||
return self.program.base if self.program.base is not None else 0
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}(\n\tsetion={},\n\tprogram={}\n)".format(
|
|
||||||
self.__class__.__name__, self.section, self.program
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType):
|
|
||||||
if context.section is None:
|
|
||||||
raise ParseException(
|
|
||||||
"Error, expected to be in {} section, but no section is present...".format(
|
|
||||||
type.name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if context.section.type != type:
|
|
||||||
raise ParseException(
|
|
||||||
"Error, expected to be in {} section, but currently in {}...".format(
|
|
||||||
type.name, context.section
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AssemblerDirectives:
|
|
||||||
"""
|
|
||||||
This class represents a collection of all assembler directives as documented by
|
|
||||||
https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#pseudo-ops
|
|
||||||
|
|
||||||
All class methods prefixed with op_ are directly used as assembler directives.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_align(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
|
|
||||||
align_to = parse_numeric_argument(args[0])
|
|
||||||
current_mod = context.current_address() % align_to
|
|
||||||
if current_mod == 0:
|
|
||||||
return
|
|
||||||
context.section.data += bytearray(align_to - current_mod)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_section(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
if get_section_base_name(args[0]) in INSTRUCTION_SECTION_NAMES:
|
|
||||||
context.new_section(args[0], MemorySectionType.Instructions)
|
|
||||||
else:
|
|
||||||
context.new_section(args[0], MemorySectionType.Data)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_globl(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
context.program.global_labels.add(args[0])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_global(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
cls.op_globl(token, args, context)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_equ(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 2)
|
|
||||||
name = args[0]
|
|
||||||
value = parse_numeric_argument(args[1])
|
|
||||||
context.context.labels[name] = value
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_space(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
|
|
||||||
|
|
||||||
size = parse_numeric_argument(args[0])
|
|
||||||
cls.add_bytes(size, None, context)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def op_zero(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
|
|
||||||
size = parse_numeric_argument(args[0])
|
|
||||||
cls.add_bytes(size, bytearray(size), context)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_bytes(
|
|
||||||
cls, size: int, content: Union[None, int, bytearray], context: ParseContext
|
|
||||||
):
|
|
||||||
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
|
|
||||||
|
|
||||||
if content is None:
|
|
||||||
content = bytearray(size)
|
|
||||||
if isinstance(content, int):
|
|
||||||
content = Int32(content).to_bytes(size)
|
|
||||||
|
|
||||||
context.section.data += content
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True):
|
|
||||||
# replace '\t' and '\n' escape sequences
|
|
||||||
text = text.replace("\\n", "\n").replace("\\t", "\t")
|
|
||||||
|
|
||||||
encoded_bytes = bytearray(text.encode("ascii"))
|
|
||||||
if zero_terminate:
|
|
||||||
encoded_bytes += bytearray(1)
|
|
||||||
cls.add_bytes(len(encoded_bytes), encoded_bytes, context)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
op = token.value[1:]
|
|
||||||
if hasattr(cls, "op_" + op):
|
|
||||||
getattr(cls, "op_" + op)(token, args, context)
|
|
||||||
elif op in ("text", "data", "rodata", "bss", "sbss"):
|
|
||||||
cls.op_section(token, (token.value,), context)
|
|
||||||
elif op in ("string", "asciiz", "asciz", "ascii"):
|
|
||||||
ASSERT_LEN(args, 1)
|
|
||||||
cls.add_text(args[0], context, zero_terminate=(op != "ascii"))
|
|
||||||
elif op in DATA_OP_SIZES:
|
|
||||||
size = DATA_OP_SIZES[op]
|
|
||||||
for arg in args:
|
|
||||||
cls.add_bytes(size, parse_numeric_argument(arg), context)
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
FMT_PARSE
|
|
||||||
+ "Unknown assembler directive: {} {} in {}".format(
|
|
||||||
token, args, context
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DATA_OP_SIZES = {
|
|
||||||
"byte": 1,
|
|
||||||
"2byte": 2,
|
|
||||||
"half": 2,
|
|
||||||
"short": 2,
|
|
||||||
"4byte": 4,
|
|
||||||
"word": 4,
|
|
||||||
"long": 4,
|
|
||||||
"8byte": 8,
|
|
||||||
"dword": 8,
|
|
||||||
"quad": 8,
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
from .formats import (
|
|
||||||
INSTRUCTION_ARGS_DECODER,
|
|
||||||
op,
|
|
||||||
decode_i,
|
|
||||||
decode_r,
|
|
||||||
decode_u,
|
|
||||||
decode_b,
|
|
||||||
decode_j,
|
|
||||||
decode_s,
|
|
||||||
decode_i_shamt,
|
|
||||||
decode_i_unsigned,
|
|
||||||
)
|
|
||||||
from .regs import RISCV_REGS
|
|
||||||
|
|
||||||
|
|
||||||
def int_to_hex(num: int):
|
|
||||||
if num < 0:
|
|
||||||
return f"-0x{-num:x}"
|
|
||||||
return f"0x{num:x}"
|
|
||||||
|
|
||||||
|
|
||||||
def format_ins(ins: int, name: str, fmt: str = "int"):
|
|
||||||
opcode = op(ins)
|
|
||||||
if fmt == "hex":
|
|
||||||
fmt = int_to_hex
|
|
||||||
else:
|
|
||||||
fmt = str
|
|
||||||
|
|
||||||
if opcode not in INSTRUCTION_ARGS_DECODER:
|
|
||||||
return f"{name} <unknown op>"
|
|
||||||
|
|
||||||
decoder = INSTRUCTION_ARGS_DECODER[opcode]
|
|
||||||
if name in ("ecall", "ebreak", "mret", "sret", "uret"):
|
|
||||||
return name
|
|
||||||
if opcode in (0x8, 0x0):
|
|
||||||
r1, r2, imm = decoder(ins)
|
|
||||||
return f"{name:<7} {RISCV_REGS[r1]}, {imm}({RISCV_REGS[r2]})"
|
|
||||||
elif decoder in (decode_i, decode_i_unsigned, decode_b, decode_i_shamt, decode_s):
|
|
||||||
r1, r2, imm = decoder(ins)
|
|
||||||
r1, r2 = RISCV_REGS[r1], RISCV_REGS[r2]
|
|
||||||
return f"{name:<7} {r1}, {r2}, {fmt(imm)}"
|
|
||||||
elif decoder in (decode_r,):
|
|
||||||
rd, rs1, rs2 = [RISCV_REGS[x] for x in decoder(ins)]
|
|
||||||
return f"{name:<7} {rd}, {rs1}, {rs2}"
|
|
||||||
elif decoder in (decode_j, decode_u):
|
|
||||||
r1, imm = decoder(ins)
|
|
||||||
return f"{name:<7} {RISCV_REGS[r1]}, {fmt(imm)}"
|
|
||||||
|
|
||||||
return f"{name} <unknown decoder>"
|
|
@ -1,34 +1,6 @@
|
|||||||
RISCV_REGS = [
|
RISCV_REGS = [
|
||||||
"zero",
|
'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2',
|
||||||
"ra",
|
's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
|
||||||
"sp",
|
's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
|
||||||
"gp",
|
't3', 't4', 't5', 't6'
|
||||||
"tp",
|
|
||||||
"t0",
|
|
||||||
"t1",
|
|
||||||
"t2",
|
|
||||||
"s0",
|
|
||||||
"s1",
|
|
||||||
"a0",
|
|
||||||
"a1",
|
|
||||||
"a2",
|
|
||||||
"a3",
|
|
||||||
"a4",
|
|
||||||
"a5",
|
|
||||||
"a6",
|
|
||||||
"a7",
|
|
||||||
"s2",
|
|
||||||
"s3",
|
|
||||||
"s4",
|
|
||||||
"s5",
|
|
||||||
"s6",
|
|
||||||
"s7",
|
|
||||||
"s8",
|
|
||||||
"s9",
|
|
||||||
"s10",
|
|
||||||
"s11",
|
|
||||||
"t3",
|
|
||||||
"t4",
|
|
||||||
"t5",
|
|
||||||
"t6",
|
|
||||||
]
|
]
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
from .instruction_set import InstructionSet, Instruction
|
|
||||||
|
|
||||||
|
|
||||||
class RV_Debug(InstructionSet):
|
|
||||||
def instruction_print(self, ins: Instruction):
|
|
||||||
reg = ins.get_reg(0)
|
|
||||||
print("register {} contains value {}".format(reg, self.regs.get(reg)))
|
|
||||||
|
|
||||||
def instruction_print_float(self, ins: Instruction):
|
|
||||||
reg = ins.get_reg(0)
|
|
||||||
print("register {} contains value {}".format(reg, self.regs.get_f(reg).value))
|
|
||||||
|
|
||||||
def instruction_print_uint(self, ins: Instruction):
|
|
||||||
reg = ins.get_reg(0)
|
|
||||||
print(
|
|
||||||
"register {} contains value {}".format(
|
|
||||||
reg, self.regs.get(reg).unsigned_value
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def instruction_print_hex(self, ins: Instruction):
|
|
||||||
reg = ins.get_reg(0)
|
|
||||||
print(
|
|
||||||
"register {} contains value {}".format(reg, hex(self.regs.get(reg).value))
|
|
||||||
)
|
|
||||||
|
|
||||||
def instruction_print_uhex(self, ins: Instruction):
|
|
||||||
reg = ins.get_reg(0)
|
|
||||||
print(
|
|
||||||
"register {} contains value {}".format(
|
|
||||||
reg, hex(self.regs.get(reg).unsigned_value)
|
|
||||||
)
|
|
||||||
)
|
|
@ -1,36 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from riscemu import RunConfig
|
|
||||||
from riscemu.types import InstructionMemorySection, SimpleInstruction, Program
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from .CPU import UserModeCPU
|
|
||||||
from .instructions import InstructionSetDict
|
|
||||||
from .debug import launch_debug_session
|
|
||||||
|
|
||||||
cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4))
|
|
||||||
|
|
||||||
program = Program("interactive session", base=0x100)
|
|
||||||
context = program.context
|
|
||||||
program.add_section(
|
|
||||||
InstructionMemorySection(
|
|
||||||
[
|
|
||||||
SimpleInstruction("ebreak", (), context, 0x100),
|
|
||||||
SimpleInstruction("addi", ("a0", "zero", "0"), context, 0x104),
|
|
||||||
SimpleInstruction("addi", ("a7", "zero", "93"), context, 0x108),
|
|
||||||
SimpleInstruction("scall", (), context, 0x10C),
|
|
||||||
],
|
|
||||||
".text",
|
|
||||||
context,
|
|
||||||
program.name,
|
|
||||||
0x100,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
cpu.load_program(program)
|
|
||||||
|
|
||||||
cpu.setup_stack()
|
|
||||||
|
|
||||||
cpu.launch()
|
|
||||||
|
|
||||||
sys.exit(cpu.exit_code)
|
|
@ -1,143 +0,0 @@
|
|||||||
"""
|
|
||||||
RiscEmu (c) 2021 Anton Lydike
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from typing import Dict, Tuple, Iterable, Callable, List
|
|
||||||
|
|
||||||
from .assembler import MemorySectionType, ParseContext, AssemblerDirectives
|
|
||||||
from .colors import FMT_PARSE
|
|
||||||
from .helpers import Peekable
|
|
||||||
from .tokenizer import Token, TokenType, tokenize
|
|
||||||
from .types import Program, T_ParserOpts, ProgramLoader, SimpleInstruction
|
|
||||||
from .types.exceptions import ParseException
|
|
||||||
|
|
||||||
|
|
||||||
def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
if context.section is None:
|
|
||||||
context.new_section(".text", MemorySectionType.Instructions)
|
|
||||||
if context.section.type != MemorySectionType.Instructions:
|
|
||||||
raise ParseException(
|
|
||||||
"{} {} encountered in invalid context: {}".format(token, args, context)
|
|
||||||
)
|
|
||||||
ins = SimpleInstruction(
|
|
||||||
token.value, args, context.context, context.current_address()
|
|
||||||
)
|
|
||||||
context.section.data.append(ins)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_label(token: Token, args: Tuple[str], context: ParseContext):
|
|
||||||
name = token.value[:-1]
|
|
||||||
if re.match(r"^\d+$", name):
|
|
||||||
# relative label:
|
|
||||||
context.context.numbered_labels[name].append(context.current_address())
|
|
||||||
else:
|
|
||||||
if name in context.context.labels:
|
|
||||||
print(FMT_PARSE + "Warn: Symbol {} defined twice!".format(name))
|
|
||||||
context.add_label(name, context.current_address(), is_relative=True)
|
|
||||||
|
|
||||||
|
|
||||||
PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {
|
|
||||||
TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction,
|
|
||||||
TokenType.LABEL: parse_label,
|
|
||||||
TokenType.INSTRUCTION_NAME: parse_instruction,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program:
|
|
||||||
"""
|
|
||||||
Convert a token stream into a parsed program
|
|
||||||
:param name: the programs name
|
|
||||||
:param tokens_iter: the programs content, tokenized
|
|
||||||
:return: a parsed program
|
|
||||||
"""
|
|
||||||
context = ParseContext(name)
|
|
||||||
|
|
||||||
for token, args in composite_tokenizer(Peekable[Token](tokens_iter)):
|
|
||||||
if token.type not in PARSERS:
|
|
||||||
raise ParseException("Unexpected token type: {}, {}".format(token, args))
|
|
||||||
PARSERS[token.type](token, args, context)
|
|
||||||
|
|
||||||
return context.finalize()
|
|
||||||
|
|
||||||
|
|
||||||
def composite_tokenizer(
|
|
||||||
tokens_iter: Iterable[Token],
|
|
||||||
) -> Iterable[Tuple[Token, Tuple[str]]]:
|
|
||||||
"""
|
|
||||||
Convert an iterator over tokens into an iterator over tuples: (token, list(token))
|
|
||||||
|
|
||||||
The first token ist either a pseudo_op, label, or instruction name. The token list are all remaining tokens before
|
|
||||||
a newline is encountered
|
|
||||||
:param tokens_iter: An iterator over tokens
|
|
||||||
:return: An iterator over a slightly more structured representation of the tokens
|
|
||||||
"""
|
|
||||||
tokens: Peekable[Token] = Peekable[Token](tokens_iter)
|
|
||||||
|
|
||||||
while not tokens.is_empty():
|
|
||||||
token = next(tokens)
|
|
||||||
if token.type in (
|
|
||||||
TokenType.PSEUDO_OP,
|
|
||||||
TokenType.LABEL,
|
|
||||||
TokenType.INSTRUCTION_NAME,
|
|
||||||
):
|
|
||||||
yield token, tuple(take_arguments(tokens))
|
|
||||||
|
|
||||||
|
|
||||||
def take_arguments(tokens: Peekable[Token]) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Consumes (argument comma)* and yields argument.value until newline is reached
|
|
||||||
If an argument is not followed by either a newline or a comma, a parse exception is raised
|
|
||||||
The newline at the end is consumed
|
|
||||||
:param tokens: A Peekable iterator over some Tokens
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
if tokens.peek().type == TokenType.ARGUMENT:
|
|
||||||
yield next(tokens).value
|
|
||||||
elif tokens.peek().type == TokenType.NEWLINE:
|
|
||||||
next(tokens)
|
|
||||||
break
|
|
||||||
elif tokens.peek().type == TokenType.COMMA:
|
|
||||||
next(tokens)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
# raise ParseException("Expected newline, instead got {}".format(tokens.peek()))
|
|
||||||
|
|
||||||
|
|
||||||
class AssemblyFileLoader(ProgramLoader):
|
|
||||||
"""
|
|
||||||
This class loads assembly files written by hand. It understands some assembler directives and supports most
|
|
||||||
pseudo instructions. It does very little verification of source correctness.
|
|
||||||
|
|
||||||
It also supports numbered jump targets and properly supports local and global scope (.globl assembly directive)
|
|
||||||
|
|
||||||
|
|
||||||
The AssemblyFileLoader loads .asm, .S and .s files by default, and acts as a weak fallback to all other filetypes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def parse(self) -> Program:
|
|
||||||
with open(self.source_path, "r") as f:
|
|
||||||
return parse_tokens(self.filename, tokenize(f))
|
|
||||||
|
|
||||||
def parse_io(self, io: Iterable[str]):
|
|
||||||
return parse_tokens(self.filename, tokenize(io))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def can_parse(cls, source_path: str) -> float:
|
|
||||||
"""
|
|
||||||
|
|
||||||
It also acts as a weak fallback if no other loaders want to take the file.
|
|
||||||
|
|
||||||
:param source_path: the path to the source file
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
# gcc recognizes these line endings as assembly. So we will do too.
|
|
||||||
if source_path.split(".")[-1] in ("asm", "S", "s"):
|
|
||||||
return 1
|
|
||||||
return 0.01
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_options(cls, argv: List[str]) -> Tuple[List[str], T_ParserOpts]:
|
|
||||||
return argv, {}
|
|
@ -1,80 +1,80 @@
|
|||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str] = {
|
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= {
|
||||||
(1, 0): "User software interrupt",
|
(1, 0): 'User software interrupt',
|
||||||
(1, 1): "Supervisor software interrupt",
|
(1, 1): 'Supervisor software interrupt',
|
||||||
(1, 3): "Machine software interrupt",
|
(1, 3): 'Machine software interrupt',
|
||||||
(1, 4): "User timer interrupt",
|
(1, 4): 'User timer interrupt',
|
||||||
(1, 5): "Supervisor timer interrupt",
|
(1, 5): 'Supervisor timer interrupt',
|
||||||
(1, 7): "Machine timer interrupt",
|
(1, 7): 'Machine timer interrupt',
|
||||||
(1, 8): "User external interrupt",
|
(1, 8): 'User external interrupt',
|
||||||
(1, 9): "Supervisor external interrupt",
|
(1, 9): 'Supervisor external interrupt',
|
||||||
(1, 11): "Machine external interrupt",
|
(1, 11): 'Machine external interrupt',
|
||||||
(0, 0): "Instruction address misaligned",
|
(0, 0): 'Instruction address misaligned',
|
||||||
(0, 1): "Instruction access fault",
|
(0, 1): 'Instruction access fault',
|
||||||
(0, 2): "Illegal instruction",
|
(0, 2): 'Illegal instruction',
|
||||||
(0, 3): "Breakpoint",
|
(0, 3): 'Breakpoint',
|
||||||
(0, 4): "Load address misaligned",
|
(0, 4): 'Load address misaligned',
|
||||||
(0, 5): "Load access fault",
|
(0, 5): 'Load access fault',
|
||||||
(0, 6): "Store/AMO address misaligned",
|
(0, 6): 'Store/AMO address misaligned',
|
||||||
(0, 7): "Store/AMO access fault",
|
(0, 7): 'Store/AMO access fault',
|
||||||
(0, 8): "environment call from user mode",
|
(0, 8): 'environment call from user mode',
|
||||||
(0, 9): "environment call from supervisor mode",
|
(0, 9): 'environment call from supervisor mode',
|
||||||
(0, 11): "environment call from machine mode",
|
(0, 11): 'environment call from machine mode',
|
||||||
(0, 12): "Instruction page fault",
|
(0, 12): 'Instruction page fault',
|
||||||
(0, 13): "Load page fault",
|
(0, 13): 'Load page fault',
|
||||||
(0, 15): "Store/AMO page fault",
|
(0, 15): 'Store/AMO page fault',
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
Assigns tuple (interrupt bit, exception code) to their respective readable names
|
Assigns tuple (interrupt bit, exception code) to their respective readable names
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MSTATUS_OFFSETS: Dict[str, int] = {
|
MSTATUS_OFFSETS: Dict[str, int] = {
|
||||||
"uie": 0,
|
'uie': 0,
|
||||||
"sie": 1,
|
'sie': 1,
|
||||||
"mie": 3,
|
'mie': 3,
|
||||||
"upie": 4,
|
'upie': 4,
|
||||||
"spie": 5,
|
'spie': 5,
|
||||||
"mpie": 7,
|
'mpie': 7,
|
||||||
"spp": 8,
|
'spp': 8,
|
||||||
"mpp": 11,
|
'mpp': 11,
|
||||||
"fs": 13,
|
'fs': 13,
|
||||||
"xs": 15,
|
'xs': 15,
|
||||||
"mpriv": 17,
|
'mpriv': 17,
|
||||||
"sum": 18,
|
'sum': 18,
|
||||||
"mxr": 19,
|
'mxr': 19,
|
||||||
"tvm": 20,
|
'tvm': 20,
|
||||||
"tw": 21,
|
'tw': 21,
|
||||||
"tsr": 22,
|
'tsr': 22,
|
||||||
"sd": 31,
|
'sd': 31
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
Offsets for all mstatus bits
|
Offsets for all mstatus bits
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MSTATUS_LEN_2 = ("mpp", "fs", "xs")
|
MSTATUS_LEN_2 = ('mpp', 'fs', 'xs')
|
||||||
"""
|
"""
|
||||||
All mstatus parts that have length 2. All other mstatus parts have length 1
|
All mstatus parts that have length 2. All other mstatus parts have length 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CSR_NAME_TO_ADDR: Dict[str, int] = {
|
CSR_NAME_TO_ADDR: Dict[str, int] = {
|
||||||
"mstatus": 0x300,
|
'mstatus': 0x300,
|
||||||
"misa": 0x301,
|
'misa': 0x301,
|
||||||
"mie": 0x304,
|
'mie': 0x304,
|
||||||
"mtvec": 0x305,
|
'mtvec': 0x305,
|
||||||
"mepc": 0x341,
|
'mepc': 0x341,
|
||||||
"mcause": 0x342,
|
'mcause': 0x342,
|
||||||
"mtval": 0x343,
|
'mtval': 0x343,
|
||||||
"mip": 0x344,
|
'mip': 0x344,
|
||||||
"mvendorid": 0xF11,
|
'mvendorid': 0xF11,
|
||||||
"marchid": 0xF12,
|
'marchid': 0xF12,
|
||||||
"mimpid": 0xF13,
|
'mimpid': 0xF13,
|
||||||
"mhartid": 0xF14,
|
'mhartid': 0xF14,
|
||||||
"time": 0xC01,
|
'time': 0xc01,
|
||||||
"timeh": 0xC81,
|
'timeh': 0xc81,
|
||||||
"halt": 0x789,
|
'halt': 0x789,
|
||||||
"mtimecmp": 0x780,
|
'mtimecmp': 0x780,
|
||||||
"mtimecmph": 0x781,
|
'mtimecmph': 0x781,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
Translation for named registers
|
Translation for named registers
|
||||||
|
@ -1,51 +1,41 @@
|
|||||||
from .types import ElfMemorySection
|
|
||||||
from ..MMU import *
|
from ..MMU import *
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from .ElfLoader import ElfExecutable
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from .PrivCPU import PrivCPU
|
from .PrivCPU import PrivCPU
|
||||||
|
|
||||||
|
|
||||||
class PrivMMU(MMU):
|
class PrivMMU(MMU):
|
||||||
def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
|
cpu: 'PrivCPU'
|
||||||
# try to get an existing section
|
|
||||||
existing_sec = super().get_sec_containing(addr)
|
@abstractmethod
|
||||||
|
def get_entrypoint(self) -> int:
|
||||||
if existing_sec is not None:
|
raise
|
||||||
return existing_sec
|
|
||||||
|
def set_cpu(self, cpu: 'PrivCPU'):
|
||||||
# get section preceding empty space at addr
|
self.cpu = cpu
|
||||||
sec_before = next(
|
|
||||||
(sec for sec in reversed(self.sections) if sec.end < addr), None
|
def translate_address(self, addr: int):
|
||||||
)
|
return ""
|
||||||
# get sec succeeding empty space at addr
|
|
||||||
sec_after = next((sec for sec in self.sections if sec.base > addr), None)
|
class LoadedElfMMU(PrivMMU):
|
||||||
|
def __init__(self, elf: ElfExecutable):
|
||||||
# calc start end end of "free" space
|
super().__init__(conf=RunConfig())
|
||||||
prev_sec_end = 0 if sec_before is None else sec_before.end
|
self.entrypoint = elf.symbols['_start']
|
||||||
next_sec_start = 0x7FFFFFFF if sec_after is None else sec_after.base
|
|
||||||
|
self.binaries.append(elf)
|
||||||
# start at the end of the prev section, or current address - 0xFFFF (aligned to 16 byte boundary)
|
for sec in elf.sections:
|
||||||
start = max(prev_sec_end, align_addr(addr - 0xFFFF, 16))
|
self.sections.append(sec)
|
||||||
# end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary)
|
|
||||||
end = min(next_sec_start, align_addr(addr + 0xFFFF, 16))
|
def load_bin(self, exe: Executable) -> LoadedExecutable:
|
||||||
|
raise NotImplementedError("This is a privMMU, it's initialized with a single ElfExecutable!")
|
||||||
sec = ElfMemorySection(
|
|
||||||
bytearray(end - start),
|
def allocate_section(self, name: str, req_size: int, flag: MemoryFlags):
|
||||||
".empty",
|
raise NotImplementedError("Not supported!")
|
||||||
self.global_instruction_context(),
|
|
||||||
"",
|
def get_entrypoint(self):
|
||||||
start,
|
return self.entrypoint
|
||||||
MemoryFlags(False, True),
|
|
||||||
)
|
|
||||||
self.sections.append(sec)
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
return sec
|
|
||||||
|
|
||||||
def global_instruction_context(self) -> InstructionContext:
|
|
||||||
context = InstructionContext()
|
|
||||||
context.global_symbol_dict = self.global_symbols
|
|
||||||
return context
|
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
import json
|
|
||||||
from collections import defaultdict
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from functools import lru_cache
|
|
||||||
from typing import Tuple, Dict, Set
|
|
||||||
|
|
||||||
from riscemu.colors import FMT_NONE, FMT_PARSE
|
|
||||||
from riscemu.decoder import format_ins, RISCV_REGS, decode
|
|
||||||
from riscemu.priv.Exceptions import (
|
|
||||||
InstructionAccessFault,
|
|
||||||
InstructionAddressMisalignedTrap,
|
|
||||||
LoadAccessFault,
|
|
||||||
)
|
|
||||||
from riscemu.types import (
|
|
||||||
Instruction,
|
|
||||||
InstructionContext,
|
|
||||||
T_RelativeAddress,
|
|
||||||
MemoryFlags,
|
|
||||||
T_AbsoluteAddress,
|
|
||||||
BinaryDataMemorySection,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class ElfInstruction(Instruction):
|
|
||||||
name: str
|
|
||||||
args: Tuple[int]
|
|
||||||
encoded: int
|
|
||||||
|
|
||||||
def get_imm(self, num: int) -> int:
|
|
||||||
return self.args[num]
|
|
||||||
|
|
||||||
def get_imm_reg(self, num: int) -> Tuple[int, int]:
|
|
||||||
return self.args[-1], self.args[-2]
|
|
||||||
|
|
||||||
def get_reg(self, num: int) -> str:
|
|
||||||
return RISCV_REGS[self.args[num]]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
if self.name == "jal" and self.args[0] == 0:
|
|
||||||
return "j {}".format(self.args[1])
|
|
||||||
if self.name == "addi" and self.args[2] == 0:
|
|
||||||
return "mv {}, {}".format(self.get_reg(0), self.get_reg(1))
|
|
||||||
if self.name == "addi" and self.args[1] == 0:
|
|
||||||
return "li {}, {}".format(self.get_reg(0), self.args[2])
|
|
||||||
if self.name == "ret" and len(self.args) == 0:
|
|
||||||
return "ret"
|
|
||||||
return format_ins(self.encoded, self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class ElfMemorySection(BinaryDataMemorySection):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
data: bytearray,
|
|
||||||
name: str,
|
|
||||||
context: InstructionContext,
|
|
||||||
owner: str,
|
|
||||||
base: int,
|
|
||||||
flags: MemoryFlags,
|
|
||||||
):
|
|
||||||
super().__init__(data, name, context, owner, base=base, flags=flags)
|
|
||||||
self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins)
|
|
||||||
|
|
||||||
def read_ins(self, offset):
|
|
||||||
if not self.flags.executable:
|
|
||||||
print(
|
|
||||||
FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE
|
|
||||||
)
|
|
||||||
raise InstructionAccessFault(offset + self.base)
|
|
||||||
if offset % 4 != 0:
|
|
||||||
raise InstructionAddressMisalignedTrap(offset + self.base)
|
|
||||||
return ElfInstruction(*decode(self.data[offset : offset + 4]))
|
|
||||||
|
|
||||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
|
||||||
if self.flags.read_only:
|
|
||||||
raise LoadAccessFault(
|
|
||||||
"read-only section", offset + self.base, size, "write"
|
|
||||||
)
|
|
||||||
self.read_ins.cache_clear()
|
|
||||||
return super(ElfMemorySection, self).write(offset, size, data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end(self):
|
|
||||||
return self.size + self.base
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryImageDebugInfos:
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
"""
|
|
||||||
Schema version
|
|
||||||
"""
|
|
||||||
|
|
||||||
base: T_AbsoluteAddress = 0
|
|
||||||
"""
|
|
||||||
The base address where the image starts. Defaults to zero.
|
|
||||||
"""
|
|
||||||
|
|
||||||
sections: Dict[str, Dict[str, Tuple[int, int]]]
|
|
||||||
"""
|
|
||||||
This dictionary maps a program and section to (start address, section length)
|
|
||||||
"""
|
|
||||||
|
|
||||||
symbols: Dict[str, Dict[str, int]]
|
|
||||||
"""
|
|
||||||
This dictionary maps a program and a symbol to a value
|
|
||||||
"""
|
|
||||||
|
|
||||||
globals: Dict[str, Set[str]]
|
|
||||||
"""
|
|
||||||
This dictionary contains the list of all global symbols of a given program
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
sections: Dict[str, Dict[str, Tuple[int, int]]],
|
|
||||||
symbols: Dict[str, Dict[str, int]],
|
|
||||||
globals: Dict[str, Set[str]],
|
|
||||||
base: int = 0,
|
|
||||||
):
|
|
||||||
self.sections = sections
|
|
||||||
self.symbols = symbols
|
|
||||||
self.globals = globals
|
|
||||||
for name in globals:
|
|
||||||
globals[name] = set(globals[name])
|
|
||||||
self.base = base
|
|
||||||
|
|
||||||
def serialize(self) -> str:
|
|
||||||
def serialize(obj: any) -> str:
|
|
||||||
if isinstance(obj, defaultdict):
|
|
||||||
return json.dumps(dict(obj), default=serialize)
|
|
||||||
if isinstance(obj, (set, tuple)):
|
|
||||||
return json.dumps(list(obj), default=serialize)
|
|
||||||
return "<<unserializable {}>>".format(
|
|
||||||
getattr(obj, "__qualname__", "{unknown}")
|
|
||||||
)
|
|
||||||
|
|
||||||
return json.dumps(
|
|
||||||
dict(
|
|
||||||
sections=self.sections,
|
|
||||||
symbols=self.symbols,
|
|
||||||
globals=self.globals,
|
|
||||||
base=self.base,
|
|
||||||
VERSION=self.VERSION,
|
|
||||||
),
|
|
||||||
default=serialize,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls, serialized_str: str) -> "MemoryImageDebugInfos":
|
|
||||||
json_obj: dict = json.loads(serialized_str)
|
|
||||||
|
|
||||||
if "VERSION" not in json_obj:
|
|
||||||
raise RuntimeError("Unknown MemoryImageDebugInfo version!")
|
|
||||||
|
|
||||||
version: str = json_obj.pop("VERSION")
|
|
||||||
|
|
||||||
# compare major version
|
|
||||||
if (
|
|
||||||
version != cls.VERSION
|
|
||||||
and version.split(".")[0] != cls.VERSION.split(".")[0]
|
|
||||||
):
|
|
||||||
raise RuntimeError(
|
|
||||||
"Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format(
|
|
||||||
cls.VERSION, version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return MemoryImageDebugInfos(**json_obj)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def builder(cls) -> "MemoryImageDebugInfos":
|
|
||||||
return MemoryImageDebugInfos(
|
|
||||||
defaultdict(dict), defaultdict(dict), defaultdict(set)
|
|
||||||
)
|
|
@ -1,248 +0,0 @@
|
|||||||
"""
|
|
||||||
RiscEmu (c) 2023 Anton Lydike
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from .helpers import *
|
|
||||||
|
|
||||||
from .types import Int32, Float32
|
|
||||||
|
|
||||||
|
|
||||||
class Registers:
|
|
||||||
"""
|
|
||||||
Represents a bunch of registers
|
|
||||||
"""
|
|
||||||
|
|
||||||
valid_regs = {
|
|
||||||
"zero",
|
|
||||||
"ra",
|
|
||||||
"sp",
|
|
||||||
"gp",
|
|
||||||
"tp",
|
|
||||||
"s0",
|
|
||||||
"fp",
|
|
||||||
"t0",
|
|
||||||
"t1",
|
|
||||||
"t2",
|
|
||||||
"t3",
|
|
||||||
"t4",
|
|
||||||
"t5",
|
|
||||||
"t6",
|
|
||||||
"s1",
|
|
||||||
"s2",
|
|
||||||
"s3",
|
|
||||||
"s4",
|
|
||||||
"s5",
|
|
||||||
"s6",
|
|
||||||
"s7",
|
|
||||||
"s8",
|
|
||||||
"s9",
|
|
||||||
"s10",
|
|
||||||
"s11",
|
|
||||||
"a0",
|
|
||||||
"a1",
|
|
||||||
"a2",
|
|
||||||
"a3",
|
|
||||||
"a4",
|
|
||||||
"a5",
|
|
||||||
"a6",
|
|
||||||
"a7",
|
|
||||||
}
|
|
||||||
|
|
||||||
float_regs = {
|
|
||||||
"ft0",
|
|
||||||
"ft1",
|
|
||||||
"ft2",
|
|
||||||
"ft3",
|
|
||||||
"ft4",
|
|
||||||
"ft5",
|
|
||||||
"ft6",
|
|
||||||
"ft7",
|
|
||||||
"fs0",
|
|
||||||
"fs1",
|
|
||||||
"fs2",
|
|
||||||
"fs3",
|
|
||||||
"fs4",
|
|
||||||
"fs5",
|
|
||||||
"fs6",
|
|
||||||
"fs7",
|
|
||||||
"fs8",
|
|
||||||
"fs9",
|
|
||||||
"fs10",
|
|
||||||
"fs11",
|
|
||||||
"fa0",
|
|
||||||
"fa1",
|
|
||||||
"fa2",
|
|
||||||
"fa3",
|
|
||||||
"fa4",
|
|
||||||
"fa5",
|
|
||||||
"fa6",
|
|
||||||
"fa7",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, infinite_regs: bool = False):
|
|
||||||
self.vals: dict[str, Int32] = defaultdict(UInt32)
|
|
||||||
self.float_vals: dict[str, Float32] = defaultdict(Float32)
|
|
||||||
|
|
||||||
self.last_set = None
|
|
||||||
self.last_read = None
|
|
||||||
|
|
||||||
self.infinite_regs = infinite_regs
|
|
||||||
|
|
||||||
def dump(self, full: bool = False):
|
|
||||||
"""
|
|
||||||
Dump all registers to stdout
|
|
||||||
:param full: If True, floating point registers are dumped too
|
|
||||||
"""
|
|
||||||
named_regs = [self._reg_repr(reg) for reg in Registers.named_registers()]
|
|
||||||
|
|
||||||
lines: list[list[str]] = [[] for _ in range(12)]
|
|
||||||
if not full:
|
|
||||||
regs = [("a", 8), ("s", 12), ("t", 7)]
|
|
||||||
else:
|
|
||||||
regs = [
|
|
||||||
("a", 8),
|
|
||||||
("s", 12),
|
|
||||||
("t", 7),
|
|
||||||
("ft", 8),
|
|
||||||
("fa", 8),
|
|
||||||
("fs", 12),
|
|
||||||
]
|
|
||||||
for name, s in regs:
|
|
||||||
for i in range(12):
|
|
||||||
if i >= s:
|
|
||||||
lines[i].append(" " * 15)
|
|
||||||
else:
|
|
||||||
reg = "{}{}".format(name, i)
|
|
||||||
lines[i].append(self._reg_repr(reg))
|
|
||||||
|
|
||||||
print(
|
|
||||||
"Registers[{},{}](".format(
|
|
||||||
FMT_ORANGE + FMT_UNDERLINE + "read" + FMT_NONE,
|
|
||||||
FMT_ORANGE + FMT_BOLD + "written" + FMT_NONE,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not full:
|
|
||||||
print("\t" + " ".join(named_regs[0:3]))
|
|
||||||
print("\t" + " ".join(named_regs[3:]))
|
|
||||||
print("\t" + "--------------- " * 3)
|
|
||||||
else:
|
|
||||||
print("\t" + " ".join(named_regs))
|
|
||||||
print("\t" + "--------------- " * 6)
|
|
||||||
for line in lines:
|
|
||||||
print("\t" + " ".join(line))
|
|
||||||
print(")")
|
|
||||||
|
|
||||||
def dump_reg_a(self):
|
|
||||||
"""
|
|
||||||
Dump the a registers
|
|
||||||
"""
|
|
||||||
print(
|
|
||||||
"Registers[a]:"
|
|
||||||
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8))
|
|
||||||
)
|
|
||||||
|
|
||||||
def _reg_repr(self, reg: str, name_len=4, fmt="08X"):
|
|
||||||
if reg in self.float_regs:
|
|
||||||
txt = "{:{}}={: .3e}".format(reg, name_len, self.get_f(reg, False))
|
|
||||||
else:
|
|
||||||
txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt)
|
|
||||||
if reg == "fp":
|
|
||||||
reg = "s0"
|
|
||||||
if reg == self.last_set:
|
|
||||||
return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE
|
|
||||||
if reg == self.last_read:
|
|
||||||
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
|
|
||||||
if reg == "zero":
|
|
||||||
return txt
|
|
||||||
should_grayscale_int = (
|
|
||||||
reg in self.valid_regs
|
|
||||||
and self.get(reg, False) == 0
|
|
||||||
and reg not in Registers.named_registers()
|
|
||||||
)
|
|
||||||
should_grayscale_float = reg in self.float_regs and self.get_f(reg, False) == 0
|
|
||||||
if should_grayscale_int or should_grayscale_float:
|
|
||||||
return FMT_GRAY + txt + FMT_NONE
|
|
||||||
return txt
|
|
||||||
|
|
||||||
def set(self, reg: str, val: "Int32", mark_set: bool = True) -> bool:
|
|
||||||
"""
|
|
||||||
Set a register content to val
|
|
||||||
:param reg: The register to set
|
|
||||||
:param val: The new value
|
|
||||||
:param mark_set: If True, marks this register as "last accessed" (only used internally)
|
|
||||||
:return: If the operation was successful
|
|
||||||
"""
|
|
||||||
|
|
||||||
# remove after refactoring is complete
|
|
||||||
if not isinstance(val, Int32):
|
|
||||||
raise RuntimeError(
|
|
||||||
"Setting register to non-Int32 value! Please refactor your code!"
|
|
||||||
)
|
|
||||||
|
|
||||||
if reg == "zero":
|
|
||||||
return False
|
|
||||||
# if reg not in Registers.all_registers():
|
|
||||||
# raise InvalidRegisterException(reg)
|
|
||||||
# replace fp register with s1, as these are the same register
|
|
||||||
if reg == "fp":
|
|
||||||
reg = "s1"
|
|
||||||
if mark_set:
|
|
||||||
self.last_set = reg
|
|
||||||
|
|
||||||
if not self.infinite_regs and reg not in self.valid_regs:
|
|
||||||
raise RuntimeError("Invalid register: {}".format(reg))
|
|
||||||
|
|
||||||
self.vals[reg] = val.unsigned()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get(self, reg: str, mark_read: bool = True) -> "Int32":
|
|
||||||
"""
|
|
||||||
Retuns the contents of register reg
|
|
||||||
:param reg: The register name
|
|
||||||
:param mark_read: If the register should be markes as "last read" (only used internally)
|
|
||||||
:return: The contents of register reg
|
|
||||||
"""
|
|
||||||
# if reg not in Registers.all_registers():
|
|
||||||
# raise InvalidRegisterException(reg)
|
|
||||||
if reg == "fp":
|
|
||||||
reg = "s0"
|
|
||||||
|
|
||||||
if not self.infinite_regs and reg not in self.valid_regs:
|
|
||||||
raise RuntimeError("Invalid register: {}".format(reg))
|
|
||||||
|
|
||||||
if mark_read:
|
|
||||||
self.last_read = reg
|
|
||||||
return self.vals[reg]
|
|
||||||
|
|
||||||
def get_f(self, reg: str, mark_read: bool = True) -> "Float32":
|
|
||||||
if not self.infinite_regs and reg not in self.float_regs:
|
|
||||||
raise RuntimeError("Invalid float register: {}".format(reg))
|
|
||||||
if mark_read:
|
|
||||||
self.last_read = reg
|
|
||||||
return self.float_vals[reg]
|
|
||||||
|
|
||||||
def set_f(self, reg: str, val: Union[float, "Float32"]):
|
|
||||||
if not self.infinite_regs and reg not in self.float_regs:
|
|
||||||
raise RuntimeError("Invalid float register: {}".format(reg))
|
|
||||||
self.float_vals[reg] = Float32(val)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def named_registers():
|
|
||||||
"""
|
|
||||||
Return all named registers
|
|
||||||
:return: The list
|
|
||||||
"""
|
|
||||||
return ["zero", "ra", "sp", "gp", "tp", "fp"]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Registers[{}]{}>".format(
|
|
||||||
"float" if self.supports_floats else "nofloat",
|
|
||||||
"{"
|
|
||||||
+ ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8))
|
|
||||||
+ "}",
|
|
||||||
)
|
|
@ -1,284 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import Type, Dict, List, Optional
|
|
||||||
|
|
||||||
from riscemu import AssemblyFileLoader, __version__, __copyright__
|
|
||||||
from riscemu.types import CPU, ProgramLoader, Program
|
|
||||||
from riscemu.instructions import InstructionSet, InstructionSetDict
|
|
||||||
from riscemu.config import RunConfig
|
|
||||||
from riscemu.CPU import UserModeCPU
|
|
||||||
|
|
||||||
|
|
||||||
class RiscemuMain:
|
|
||||||
"""
|
|
||||||
This represents the riscemu API exposed to other programs for better
|
|
||||||
interoperability.
|
|
||||||
"""
|
|
||||||
|
|
||||||
available_ins_sets: Dict[str, Type[InstructionSet]]
|
|
||||||
available_file_loaders: List[Type[ProgramLoader]]
|
|
||||||
|
|
||||||
cfg: Optional[RunConfig]
|
|
||||||
cpu: Optional[CPU]
|
|
||||||
|
|
||||||
input_files: List[str]
|
|
||||||
selected_ins_sets: List[Type[InstructionSet]]
|
|
||||||
|
|
||||||
def __init__(self, cfg: Optional[RunConfig] = None):
|
|
||||||
self.available_ins_sets = dict()
|
|
||||||
self.selected_ins_sets = []
|
|
||||||
self.available_file_loaders = []
|
|
||||||
self.cfg: Optional[RunConfig] = cfg
|
|
||||||
self.cpu: Optional[CPU] = None
|
|
||||||
self.input_files = []
|
|
||||||
self.selected_ins_sets = []
|
|
||||||
|
|
||||||
def instantiate_cpu(self):
|
|
||||||
self.cpu = UserModeCPU(self.selected_ins_sets, self.cfg)
|
|
||||||
self.configure_cpu()
|
|
||||||
|
|
||||||
def configure_cpu(self):
|
|
||||||
assert self.cfg is not None
|
|
||||||
if isinstance(self.cpu, UserModeCPU) and self.cfg.stack_size != 0:
|
|
||||||
self.cpu.setup_stack(self.cfg.stack_size)
|
|
||||||
|
|
||||||
def register_all_arguments(self, parser: argparse.ArgumentParser):
|
|
||||||
parser.add_argument(
|
|
||||||
"files",
|
|
||||||
metavar="file.asm",
|
|
||||||
type=str,
|
|
||||||
nargs="+",
|
|
||||||
help="The assembly files to load, the last one will be run",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--options",
|
|
||||||
"-o",
|
|
||||||
action=OptionStringAction,
|
|
||||||
keys=(
|
|
||||||
"disable_debug",
|
|
||||||
"no_syscall_symbols",
|
|
||||||
"fail_on_ex",
|
|
||||||
"add_accept_imm",
|
|
||||||
"unlimited_regs",
|
|
||||||
"libc",
|
|
||||||
"ignore_exit_code",
|
|
||||||
),
|
|
||||||
help="""Toggle options. Available options are:
|
|
||||||
disable_debug: Disable ebreak instructions
|
|
||||||
no_syscall_symbols: Don't add symbols for SCALL_EXIT and others
|
|
||||||
fail_on_ex: If set, exceptions won't trigger the debugger
|
|
||||||
add_accept_imm: Accept "add rd, rs, imm" instruction (instead of addi)
|
|
||||||
unlimited_regs: Allow an unlimited number of registers
|
|
||||||
libc: Load a libc-like runtime (for malloc, etc.)
|
|
||||||
ignore_exit_code: Don't exit with the programs exit code.""",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--syscall-opts",
|
|
||||||
"-so",
|
|
||||||
action=OptionStringAction,
|
|
||||||
keys=("fs_access", "disable_input"),
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--instruction-sets",
|
|
||||||
"-is",
|
|
||||||
action=OptionStringAction,
|
|
||||||
help="Instruction sets to load, available are: {}. All are enabled by default".format(
|
|
||||||
", ".join(self.available_ins_sets)
|
|
||||||
),
|
|
||||||
keys={k: True for k in self.available_ins_sets},
|
|
||||||
omit_empty=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--stack_size",
|
|
||||||
type=int,
|
|
||||||
help="Stack size of loaded programs, defaults to 8MB",
|
|
||||||
nargs="?",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"-v",
|
|
||||||
"--verbose",
|
|
||||||
help="Verbosity level (can be used multiple times)",
|
|
||||||
action="count",
|
|
||||||
default=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--interactive",
|
|
||||||
help="Launch the interactive debugger instantly instead of loading any "
|
|
||||||
"programs",
|
|
||||||
action="store_true",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--ignore-exit-code",
|
|
||||||
help="Ignore exit code of the program and always return 0 if the program ran to completion.",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def register_all_isas(self):
|
|
||||||
self.available_ins_sets.update(InstructionSetDict)
|
|
||||||
|
|
||||||
def register_all_program_loaders(self):
|
|
||||||
self.available_file_loaders.append(AssemblyFileLoader)
|
|
||||||
|
|
||||||
def parse_argv(self, argv: List[str]):
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="RISC-V Userspace emulator",
|
|
||||||
prog="riscemu",
|
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
|
||||||
)
|
|
||||||
if "--version" in argv:
|
|
||||||
print(
|
|
||||||
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
|
|
||||||
__version__, __copyright__, ", ".join(self.available_ins_sets)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
self.register_all_arguments(parser)
|
|
||||||
|
|
||||||
# parse argv
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
# add ins
|
|
||||||
if not hasattr(args, "ins"):
|
|
||||||
setattr(args, "ins", {k: True for k in self.available_ins_sets})
|
|
||||||
|
|
||||||
# create RunConfig
|
|
||||||
self.cfg = self.create_config(args)
|
|
||||||
|
|
||||||
# set input files
|
|
||||||
self.input_files = args.files
|
|
||||||
|
|
||||||
# get selected ins sets
|
|
||||||
self.selected_ins_sets = list(
|
|
||||||
self.available_ins_sets[name]
|
|
||||||
for name, selected in args.ins.items()
|
|
||||||
if selected
|
|
||||||
)
|
|
||||||
|
|
||||||
# if use_libc is given, attach libc to path
|
|
||||||
if self.cfg.use_libc:
|
|
||||||
self.add_libc_to_input_files()
|
|
||||||
|
|
||||||
def add_libc_to_input_files(self):
|
|
||||||
"""
|
|
||||||
This adds the provided riscemu libc to the programs runtime.
|
|
||||||
"""
|
|
||||||
libc_path = os.path.join(
|
|
||||||
os.path.dirname(__file__),
|
|
||||||
"..",
|
|
||||||
"libc",
|
|
||||||
"*.s",
|
|
||||||
)
|
|
||||||
for path in glob.iglob(libc_path):
|
|
||||||
if path not in self.input_files:
|
|
||||||
self.input_files.append(path)
|
|
||||||
|
|
||||||
def create_config(self, args: argparse.Namespace) -> RunConfig:
|
|
||||||
# create a RunConfig from the cli args
|
|
||||||
cfg_dict = dict(
|
|
||||||
stack_size=args.stack_size,
|
|
||||||
debug_instruction=not args.options["disable_debug"],
|
|
||||||
include_scall_symbols=not args.options["no_syscall_symbols"],
|
|
||||||
debug_on_exception=not args.options["fail_on_ex"],
|
|
||||||
add_accept_imm=args.options["add_accept_imm"],
|
|
||||||
unlimited_registers=args.options["unlimited_regs"],
|
|
||||||
scall_fs=args.syscall_opts["fs_access"],
|
|
||||||
scall_input=not args.syscall_opts["disable_input"],
|
|
||||||
verbosity=args.verbose,
|
|
||||||
use_libc=args.options["libc"],
|
|
||||||
ignore_exit_code=args.options["ignore_exit_code"],
|
|
||||||
)
|
|
||||||
for k, v in dict(cfg_dict).items():
|
|
||||||
if v is None:
|
|
||||||
del cfg_dict[k]
|
|
||||||
|
|
||||||
return RunConfig(**cfg_dict)
|
|
||||||
|
|
||||||
def load_programs(self):
|
|
||||||
for path in self.input_files:
|
|
||||||
for loader in self.available_file_loaders:
|
|
||||||
if not loader.can_parse(path):
|
|
||||||
continue
|
|
||||||
programs = loader.instantiate(path, {}).parse()
|
|
||||||
if isinstance(programs, Program):
|
|
||||||
programs = [programs]
|
|
||||||
for p in programs:
|
|
||||||
self.cpu.mmu.load_program(p)
|
|
||||||
|
|
||||||
def run_from_cli(self, argv: List[str]):
|
|
||||||
# register everything
|
|
||||||
self.register_all_isas()
|
|
||||||
self.register_all_program_loaders()
|
|
||||||
|
|
||||||
# parse argv and set up cpu
|
|
||||||
self.parse_argv(argv)
|
|
||||||
self.instantiate_cpu()
|
|
||||||
self.load_programs()
|
|
||||||
|
|
||||||
# run the program
|
|
||||||
self.cpu.launch(self.cfg.verbosity > 1)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
This assumes that these values were set externally:
|
|
||||||
|
|
||||||
- available_file_loaders: A list of available file loaders.
|
|
||||||
Can be set using .register_all_program_loaders()
|
|
||||||
- cfg: The RunConfig object. Can be directly assigned to the attribute
|
|
||||||
- input_files: A list of assembly files to load.
|
|
||||||
- selected_ins_sets: A list of instruction sets the CPU should support.
|
|
||||||
"""
|
|
||||||
assert self.cfg is not None, "self.cfg must be set before calling run()"
|
|
||||||
assert self.selected_ins_sets, "self.selected_ins_sets cannot be empty"
|
|
||||||
assert self.input_files, "self.input_files cannot be empty"
|
|
||||||
|
|
||||||
if self.cfg.use_libc:
|
|
||||||
self.add_libc_to_input_files()
|
|
||||||
|
|
||||||
self.instantiate_cpu()
|
|
||||||
self.load_programs()
|
|
||||||
|
|
||||||
# run the program
|
|
||||||
self.cpu.launch(self.cfg.verbosity > 1)
|
|
||||||
|
|
||||||
|
|
||||||
class OptionStringAction(argparse.Action):
|
|
||||||
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
|
|
||||||
if keys is None:
|
|
||||||
raise ValueError('must define "keys" argument')
|
|
||||||
if isinstance(keys, dict):
|
|
||||||
keys_d = keys
|
|
||||||
elif isinstance(keys, (list, tuple)):
|
|
||||||
keys_d = {}
|
|
||||||
for k in keys:
|
|
||||||
if isinstance(k, tuple):
|
|
||||||
k, v = k
|
|
||||||
else:
|
|
||||||
v = False
|
|
||||||
keys_d[k] = v
|
|
||||||
else:
|
|
||||||
keys_d = dict()
|
|
||||||
super().__init__(option_strings, dest, default=keys_d, **kwargs)
|
|
||||||
self.keys = keys_d
|
|
||||||
self.omit_empty = omit_empty
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
d = {}
|
|
||||||
if not self.omit_empty:
|
|
||||||
d.update(self.keys)
|
|
||||||
for x in values.split(","):
|
|
||||||
if x in self.keys:
|
|
||||||
d[x] = True
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid parameter supplied: " + x)
|
|
||||||
setattr(namespace, self.dest, d)
|
|
@ -1,295 +0,0 @@
|
|||||||
"""
|
|
||||||
RiscEmu (c) 2021 Anton Lydike
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from math import log2, ceil
|
|
||||||
from typing import Dict, IO, Union
|
|
||||||
|
|
||||||
from .types import BinaryDataMemorySection, MemoryFlags
|
|
||||||
from .colors import FMT_SYSCALL, FMT_NONE
|
|
||||||
from .types import Int32, CPU
|
|
||||||
from .types.exceptions import InvalidSyscallException
|
|
||||||
|
|
||||||
SYSCALLS = {
|
|
||||||
63: "read",
|
|
||||||
64: "write",
|
|
||||||
93: "exit",
|
|
||||||
192: "mmap2",
|
|
||||||
1024: "open",
|
|
||||||
1025: "close",
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
This dict contains a mapping for all available syscalls (code->name)
|
|
||||||
|
|
||||||
If you wish to add a syscall to the built-in system, you can extend this
|
|
||||||
dictionary and implement a method with the same name on the SyscallInterface
|
|
||||||
class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ADDITIONAL_SYMBOLS = {
|
|
||||||
"MAP_PRIVATE": 1 << 0,
|
|
||||||
"MAP_SHARED": 1 << 1,
|
|
||||||
"MAP_ANON": 1 << 2,
|
|
||||||
"MAP_ANONYMOUS": 1 << 2,
|
|
||||||
"PROT_READ": 1 << 0,
|
|
||||||
"PROT_WRITE": 1 << 1,
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
A set of additional symbols that are used by various syscalls.
|
|
||||||
"""
|
|
||||||
|
|
||||||
OPEN_MODES = {
|
|
||||||
0: "rb",
|
|
||||||
1: "wb",
|
|
||||||
2: "r+b",
|
|
||||||
3: "x",
|
|
||||||
4: "ab",
|
|
||||||
}
|
|
||||||
"""All available file open modes"""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Syscall:
|
|
||||||
"""
|
|
||||||
Represents a syscall
|
|
||||||
"""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
"""The syscall number (e.g. 64 - write)"""
|
|
||||||
cpu: CPU
|
|
||||||
"""The CPU object that created the syscall"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return SYSCALLS.get(self.id, "unknown")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "Syscall(id={}, name={})".format(self.id, self.name)
|
|
||||||
|
|
||||||
def ret(self, code: Union[int, Int32]):
|
|
||||||
self.cpu.regs.set("a0", Int32(code))
|
|
||||||
|
|
||||||
|
|
||||||
def get_syscall_symbols():
|
|
||||||
"""
|
|
||||||
Generate global syscall symbols (such as SCALL_READ, SCALL_EXIT etc)
|
|
||||||
|
|
||||||
:return: dictionary of all syscall symbols (SCALL_<name> -> id)
|
|
||||||
"""
|
|
||||||
items: Dict[str, int] = {
|
|
||||||
("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
items.update(ADDITIONAL_SYMBOLS)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
|
|
||||||
class SyscallInterface:
|
|
||||||
"""
|
|
||||||
Handles syscalls
|
|
||||||
"""
|
|
||||||
|
|
||||||
open_files: Dict[int, IO]
|
|
||||||
next_open_handle: int
|
|
||||||
|
|
||||||
def handle_syscall(self, scall: Syscall):
|
|
||||||
self.next_open_handle = 3
|
|
||||||
self.open_files = {0: sys.stdin, 1: sys.stdout, 2: sys.stderr}
|
|
||||||
|
|
||||||
if getattr(self, scall.name):
|
|
||||||
getattr(self, scall.name)(scall)
|
|
||||||
else:
|
|
||||||
raise InvalidSyscallException(scall)
|
|
||||||
|
|
||||||
def read(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
read syscall (63): read from file no a0, into addr a1, at most a2 bytes
|
|
||||||
on return a0 will be the number of read bytes or -1 if an error occured
|
|
||||||
"""
|
|
||||||
fileno = scall.cpu.regs.get("a0").unsigned_value
|
|
||||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
|
||||||
size = scall.cpu.regs.get("a2").unsigned_value
|
|
||||||
if fileno not in self.open_files:
|
|
||||||
scall.ret(-1)
|
|
||||||
return
|
|
||||||
|
|
||||||
chars = self.open_files[fileno].readline(size)
|
|
||||||
try:
|
|
||||||
data = bytearray(chars, "ascii")
|
|
||||||
scall.cpu.mmu.write(addr, len(data), data)
|
|
||||||
return scall.ret(len(data))
|
|
||||||
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
def write(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
write syscall (64): write a2 bytes from addr a1 into fileno a0
|
|
||||||
on return a0 will hold the number of bytes written or -1 if an error occured
|
|
||||||
"""
|
|
||||||
fileno = scall.cpu.regs.get("a0").unsigned_value
|
|
||||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
|
||||||
size = scall.cpu.regs.get("a2").unsigned_value
|
|
||||||
if fileno not in self.open_files:
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
data = scall.cpu.mmu.read(addr, size)
|
|
||||||
|
|
||||||
if not isinstance(data, bytearray):
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] write: writing from .text region not supported."
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
self.open_files[fileno].write(data.decode("ascii"))
|
|
||||||
return scall.ret(size)
|
|
||||||
|
|
||||||
def open(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
open syscall (1024): read path of a2 bytes from addr a1, in mode a0
|
|
||||||
returns the file no in a0
|
|
||||||
|
|
||||||
modes:
|
|
||||||
- 0: read
|
|
||||||
- 1: write (truncate)
|
|
||||||
- 2: read/write (no truncate)
|
|
||||||
- 3: only create
|
|
||||||
- 4: append
|
|
||||||
|
|
||||||
Requires running with flag scall-fs
|
|
||||||
"""
|
|
||||||
# FIXME: this should be toggleable in a global setting or something
|
|
||||||
if True:
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] open: opening files not supported without scall-fs flag!"
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
mode = scall.cpu.regs.get("a0").unsigned_value
|
|
||||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
|
||||||
size = scall.cpu.regs.get("a2").unsigned_value
|
|
||||||
|
|
||||||
mode_st = OPEN_MODES.get(
|
|
||||||
mode,
|
|
||||||
)
|
|
||||||
if mode_st == -1:
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] open: unknown opening mode {}!".format(mode)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
path = scall.cpu.mmu.read(addr, size).decode("ascii")
|
|
||||||
|
|
||||||
fileno = self.next_open_handle
|
|
||||||
self.next_open_handle += 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.open_files[fileno] = open(path, mode_st)
|
|
||||||
except OSError as err:
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] open: encountered error during {}!".format(err.strerror)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] open: opened fd {} to {}!".format(fileno, path)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(fileno)
|
|
||||||
|
|
||||||
def close(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
close syscall (1025): closes file no a0
|
|
||||||
|
|
||||||
return -1 if an error was encountered, otherwise returns 0
|
|
||||||
"""
|
|
||||||
fileno = scall.cpu.regs.get("a0").unsigned_value
|
|
||||||
if fileno not in self.open_files:
|
|
||||||
print(
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "[Syscall] close: unknown fileno {}!".format(fileno)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
self.open_files[fileno].close()
|
|
||||||
print(FMT_SYSCALL + "[Syscall] close: closed fd {}!".format(fileno) + FMT_NONE)
|
|
||||||
del self.open_files[fileno]
|
|
||||||
return scall.ret(0)
|
|
||||||
|
|
||||||
def exit(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
Exit syscall. Exits the system with status code a0
|
|
||||||
"""
|
|
||||||
scall.cpu.halted = True
|
|
||||||
scall.cpu.exit_code = scall.cpu.regs.get("a0").signed().value
|
|
||||||
|
|
||||||
def mmap2(self, scall: Syscall):
|
|
||||||
"""
|
|
||||||
mmap2 syscall:
|
|
||||||
|
|
||||||
void *mmap(void *addr, size_t length, int prot, int flags,
|
|
||||||
int fd, off_t offset);
|
|
||||||
|
|
||||||
Only supported modes:
|
|
||||||
addr = <any>
|
|
||||||
prot = either PROT_READ or PROT_READ | PROT_WRITE
|
|
||||||
flags = MAP_PRIVATE | MAP_ANONYMOUS
|
|
||||||
fd = <ignored>
|
|
||||||
off_t = <ignored>
|
|
||||||
"""
|
|
||||||
addr = scall.cpu.regs.get("a0").unsigned_value
|
|
||||||
size = scall.cpu.regs.get("a1").unsigned_value
|
|
||||||
prot = scall.cpu.regs.get("a2").unsigned_value
|
|
||||||
flags = scall.cpu.regs.get("a3").unsigned_value
|
|
||||||
|
|
||||||
# error out if prot is not 1 or 3:
|
|
||||||
# 1 = PROT_READ
|
|
||||||
# 3 = PROT_READ | PROT_WRITE
|
|
||||||
if prot != 1 and prot != 3:
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
# round size up to multiple of 4096
|
|
||||||
size = 4096 * ceil(size / 4096)
|
|
||||||
section = BinaryDataMemorySection(
|
|
||||||
bytearray(size),
|
|
||||||
".data.runtime-allocated",
|
|
||||||
None,
|
|
||||||
"system",
|
|
||||||
base=addr,
|
|
||||||
flags=MemoryFlags(read_only=prot != 3, executable=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
# try to insert section
|
|
||||||
if scall.cpu.mmu.load_section(section, addr != 0):
|
|
||||||
return scall.ret(section.base)
|
|
||||||
# if that failed, and we tried to force an address,
|
|
||||||
# try again at any address
|
|
||||||
elif addr != 0:
|
|
||||||
section.base = 0
|
|
||||||
if scall.cpu.mmu.load_section(section):
|
|
||||||
return scall.ret(section.base)
|
|
||||||
# if that didn't work, return error
|
|
||||||
return scall.ret(-1)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}(\n\tfiles={}\n)".format(self.__class__.__name__, self.open_files)
|
|
@ -1,136 +0,0 @@
|
|||||||
"""
|
|
||||||
RiscEmu (c) 2021 Anton Lydike
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum, auto
|
|
||||||
from typing import List, Iterable
|
|
||||||
|
|
||||||
from riscemu.decoder import RISCV_REGS
|
|
||||||
from riscemu.types.exceptions import ParseException
|
|
||||||
|
|
||||||
LINE_COMMENT_STARTERS = ("#", ";", "//")
|
|
||||||
WHITESPACE_PATTERN = re.compile(r"\s+")
|
|
||||||
MEMORY_ADDRESS_PATTERN = re.compile(
|
|
||||||
r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]*)\)$"
|
|
||||||
)
|
|
||||||
REGISTER_NAMES = RISCV_REGS
|
|
||||||
|
|
||||||
|
|
||||||
class TokenType(Enum):
|
|
||||||
COMMA = auto()
|
|
||||||
ARGUMENT = auto()
|
|
||||||
PSEUDO_OP = auto()
|
|
||||||
INSTRUCTION_NAME = auto()
|
|
||||||
NEWLINE = auto()
|
|
||||||
LABEL = auto()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Token:
|
|
||||||
type: TokenType
|
|
||||||
value: str
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.type == TokenType.NEWLINE:
|
|
||||||
return "\\n"
|
|
||||||
if self.type == TokenType.COMMA:
|
|
||||||
return ", "
|
|
||||||
return "{}({})".format(self.type.name[0:3], self.value)
|
|
||||||
|
|
||||||
|
|
||||||
NEWLINE = Token(TokenType.NEWLINE, "\n")
|
|
||||||
COMMA = Token(TokenType.COMMA, ",")
|
|
||||||
|
|
||||||
|
|
||||||
def tokenize(input: Iterable[str]) -> Iterable[Token]:
|
|
||||||
for line in input:
|
|
||||||
for line_comment_start in LINE_COMMENT_STARTERS:
|
|
||||||
if line_comment_start in line:
|
|
||||||
line = line[: line.index(line_comment_start)]
|
|
||||||
line.strip(" \t\n")
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parts = list(part for part in split_whitespace_respecting_quotes(line) if part)
|
|
||||||
|
|
||||||
yield from parse_line(parts)
|
|
||||||
yield NEWLINE
|
|
||||||
|
|
||||||
|
|
||||||
def parse_line(parts: List[str]) -> Iterable[Token]:
|
|
||||||
if len(parts) == 0:
|
|
||||||
return ()
|
|
||||||
first_token = parts[0]
|
|
||||||
|
|
||||||
if first_token[0] == ".":
|
|
||||||
yield Token(TokenType.PSEUDO_OP, first_token)
|
|
||||||
elif first_token[-1] == ":":
|
|
||||||
yield Token(TokenType.LABEL, first_token)
|
|
||||||
yield from parse_line(parts[1:])
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
yield Token(TokenType.INSTRUCTION_NAME, first_token)
|
|
||||||
|
|
||||||
for part in parts[1:]:
|
|
||||||
if part == ",":
|
|
||||||
yield COMMA
|
|
||||||
continue
|
|
||||||
yield from parse_arg(part)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arg(arg: str) -> Iterable[Token]:
|
|
||||||
comma = arg[-1] == ","
|
|
||||||
arg = arg[:-1] if comma else arg
|
|
||||||
mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg)
|
|
||||||
if mem_match_resul:
|
|
||||||
register = mem_match_resul.group(2).lower()
|
|
||||||
immediate = mem_match_resul.group(1)
|
|
||||||
yield Token(TokenType.ARGUMENT, register)
|
|
||||||
yield Token(TokenType.ARGUMENT, immediate)
|
|
||||||
else:
|
|
||||||
yield Token(TokenType.ARGUMENT, arg)
|
|
||||||
if comma:
|
|
||||||
yield COMMA
|
|
||||||
|
|
||||||
|
|
||||||
def print_tokens(tokens: Iterable[Token]):
|
|
||||||
for token in tokens:
|
|
||||||
print(token, end="\n" if token == NEWLINE else "")
|
|
||||||
print("", flush=True, end="")
|
|
||||||
|
|
||||||
|
|
||||||
def split_whitespace_respecting_quotes(line: str) -> Iterable[str]:
|
|
||||||
quote = ""
|
|
||||||
part = ""
|
|
||||||
for c in line:
|
|
||||||
if c == quote:
|
|
||||||
yield part
|
|
||||||
part = ""
|
|
||||||
quote = ""
|
|
||||||
continue
|
|
||||||
|
|
||||||
if quote != "":
|
|
||||||
part += c
|
|
||||||
continue
|
|
||||||
|
|
||||||
if c in "\"'":
|
|
||||||
if part:
|
|
||||||
yield part
|
|
||||||
quote = c
|
|
||||||
part = ""
|
|
||||||
continue
|
|
||||||
|
|
||||||
if c in " \t\n":
|
|
||||||
if part:
|
|
||||||
yield part
|
|
||||||
part = ""
|
|
||||||
continue
|
|
||||||
|
|
||||||
part += c
|
|
||||||
|
|
||||||
if part:
|
|
||||||
yield part
|
|
@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
python3 -m riscemu "$@"
|
|
@ -1,41 +0,0 @@
|
|||||||
from typing import Dict, Any
|
|
||||||
import re
|
|
||||||
|
|
||||||
# define some base type aliases so we can keep track of absolute and relative addresses
|
|
||||||
T_RelativeAddress = int
|
|
||||||
T_AbsoluteAddress = int
|
|
||||||
|
|
||||||
# parser options are just dictionaries with arbitrary values
|
|
||||||
T_ParserOpts = Dict[str, Any]
|
|
||||||
|
|
||||||
NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$")
|
|
||||||
|
|
||||||
# base classes
|
|
||||||
from .flags import MemoryFlags
|
|
||||||
from .int32 import UInt32, Int32
|
|
||||||
from .float32 import Float32
|
|
||||||
from .instruction import Instruction
|
|
||||||
from .instruction_context import InstructionContext
|
|
||||||
from .memory_section import MemorySection
|
|
||||||
from .program import Program
|
|
||||||
from .program_loader import ProgramLoader
|
|
||||||
from .cpu import CPU
|
|
||||||
from .simple_instruction import SimpleInstruction
|
|
||||||
from .instruction_memory_section import InstructionMemorySection
|
|
||||||
from .binary_data_memory_section import BinaryDataMemorySection
|
|
||||||
|
|
||||||
# exceptions
|
|
||||||
from .exceptions import (
|
|
||||||
ParseException,
|
|
||||||
NumberFormatException,
|
|
||||||
MemoryAccessException,
|
|
||||||
OutOfMemoryException,
|
|
||||||
LinkerException,
|
|
||||||
LaunchDebuggerException,
|
|
||||||
RiscemuBaseException,
|
|
||||||
InvalidRegisterException,
|
|
||||||
InvalidAllocationException,
|
|
||||||
InvalidSyscallException,
|
|
||||||
UnimplementedInstruction,
|
|
||||||
INS_NOT_IMPLEMENTED,
|
|
||||||
)
|
|
@ -1,56 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
from . import (
|
|
||||||
MemorySection,
|
|
||||||
InstructionContext,
|
|
||||||
MemoryFlags,
|
|
||||||
T_RelativeAddress,
|
|
||||||
Instruction,
|
|
||||||
)
|
|
||||||
from ..types.exceptions import MemoryAccessException
|
|
||||||
|
|
||||||
|
|
||||||
class BinaryDataMemorySection(MemorySection):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
data: bytearray,
|
|
||||||
name: str,
|
|
||||||
context: InstructionContext,
|
|
||||||
owner: str,
|
|
||||||
base: int = 0,
|
|
||||||
flags: Optional[MemoryFlags] = None,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name,
|
|
||||||
flags if flags is not None else MemoryFlags(False, False),
|
|
||||||
len(data),
|
|
||||||
base,
|
|
||||||
owner,
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
|
||||||
if offset + size > self.size:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Out of bounds access in {}".format(self), offset, size, "read"
|
|
||||||
)
|
|
||||||
return self.data[offset : offset + size]
|
|
||||||
|
|
||||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
|
||||||
if offset + size > self.size:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Out of bounds access in {}".format(self), offset, size, "write"
|
|
||||||
)
|
|
||||||
if len(data[0:size]) != size:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Invalid write parameter sizing", offset, size, "write"
|
|
||||||
)
|
|
||||||
self.data[offset : offset + size] = data[0:size]
|
|
||||||
|
|
||||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Tried reading instruction on non-executable section {}".format(self),
|
|
||||||
offset,
|
|
||||||
4,
|
|
||||||
"instruction fetch",
|
|
||||||
)
|
|
@ -1,124 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING, Iterable
|
|
||||||
|
|
||||||
from ..registers import Registers
|
|
||||||
from ..config import RunConfig
|
|
||||||
from ..colors import FMT_NONE, FMT_CPU
|
|
||||||
from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from ..MMU import MMU
|
|
||||||
from ..instructions import InstructionSet
|
|
||||||
|
|
||||||
|
|
||||||
class CPU(ABC):
|
|
||||||
# static cpu configuration
|
|
||||||
INS_XLEN: int = 4
|
|
||||||
|
|
||||||
# housekeeping variables
|
|
||||||
regs: Registers
|
|
||||||
mmu: "MMU"
|
|
||||||
pc: T_AbsoluteAddress
|
|
||||||
cycle: int
|
|
||||||
halted: bool
|
|
||||||
|
|
||||||
# debugging context
|
|
||||||
debugger_active: bool
|
|
||||||
|
|
||||||
# instruction information
|
|
||||||
instructions: Dict[str, Callable[[Instruction], None]]
|
|
||||||
instruction_sets: Set["InstructionSet"]
|
|
||||||
|
|
||||||
# configuration
|
|
||||||
conf: RunConfig
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
mmu: "MMU",
|
|
||||||
instruction_sets: List[Type["InstructionSet"]],
|
|
||||||
conf: RunConfig,
|
|
||||||
):
|
|
||||||
self.mmu = mmu
|
|
||||||
self.regs = Registers(conf.unlimited_registers)
|
|
||||||
self.conf = conf
|
|
||||||
|
|
||||||
self.instruction_sets = set()
|
|
||||||
self.instructions = dict()
|
|
||||||
|
|
||||||
for set_class in instruction_sets:
|
|
||||||
ins_set = set_class(self)
|
|
||||||
self.instructions.update(ins_set.load())
|
|
||||||
self.instruction_sets.add(ins_set)
|
|
||||||
|
|
||||||
self.halted = False
|
|
||||||
self.cycle = 0
|
|
||||||
self.pc = 0
|
|
||||||
self.debugger_active = False
|
|
||||||
|
|
||||||
def run_instruction(self, ins: Instruction):
|
|
||||||
"""
|
|
||||||
Execute a single instruction
|
|
||||||
|
|
||||||
:param ins: The instruction to execute
|
|
||||||
"""
|
|
||||||
if ins.name in self.instructions:
|
|
||||||
self.instructions[ins.name](ins)
|
|
||||||
else:
|
|
||||||
# this should never be reached, as unknown instructions are imparseable
|
|
||||||
raise RuntimeError("Unknown instruction: {}".format(ins))
|
|
||||||
|
|
||||||
def load_program(self, program: Program):
|
|
||||||
self.mmu.load_program(program)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""
|
|
||||||
Returns a representation of the CPU and some of its state.
|
|
||||||
"""
|
|
||||||
return "{}(pc=0x{:08X}, cycle={}, halted={} instructions={})".format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.pc,
|
|
||||||
self.cycle,
|
|
||||||
self.halted,
|
|
||||||
" ".join(s.name for s in self.instruction_sets),
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def step(self, verbose: bool = False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def run(self, verbose: bool = False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def launch(self, verbose: bool = False):
|
|
||||||
entrypoint = self.mmu.find_entrypoint()
|
|
||||||
|
|
||||||
if entrypoint is None:
|
|
||||||
entrypoint = self.mmu.programs[0].entrypoint
|
|
||||||
|
|
||||||
if self.conf.verbosity > 0:
|
|
||||||
print(
|
|
||||||
FMT_CPU
|
|
||||||
+ "[CPU] Started running from {}".format(
|
|
||||||
self.mmu.translate_address(entrypoint)
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
self.pc = entrypoint
|
|
||||||
self.run(verbose)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_loaders(cls) -> Iterable[Type[ProgramLoader]]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:
|
|
||||||
return max(self.get_loaders(), key=lambda ld: ld.can_parse(file_name))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sections(self):
|
|
||||||
return self.mmu.sections
|
|
||||||
|
|
||||||
@property
|
|
||||||
def programs(self):
|
|
||||||
return self.mmu.programs
|
|
@ -1,200 +0,0 @@
|
|||||||
"""
|
|
||||||
RiscEmu (c) 2021 Anton Lydike
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
from abc import abstractmethod
|
|
||||||
from ..colors import *
|
|
||||||
import typing
|
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
|
||||||
from . import Instruction
|
|
||||||
|
|
||||||
|
|
||||||
class RiscemuBaseException(BaseException):
|
|
||||||
@abstractmethod
|
|
||||||
def message(self) -> str:
|
|
||||||
raise NotImplemented
|
|
||||||
|
|
||||||
def print_stacktrace(self):
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
traceback.print_exception(type(self), self, self.__traceback__)
|
|
||||||
|
|
||||||
|
|
||||||
# Parsing exceptions:
|
|
||||||
|
|
||||||
|
|
||||||
class ParseException(RiscemuBaseException):
|
|
||||||
def __init__(self, msg: str, data=None):
|
|
||||||
super().__init__(msg, data)
|
|
||||||
self.msg = msg
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_PARSE
|
|
||||||
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_EQ(a1, a2):
|
|
||||||
if a1 != a2:
|
|
||||||
raise ParseException(
|
|
||||||
"ASSERTION_FAILED: Expected elements to be equal!", (a1, a2)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_LEN(a1, size):
|
|
||||||
if len(a1) != size:
|
|
||||||
raise ParseException(
|
|
||||||
"ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size),
|
|
||||||
(a1, size),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_NOT_NULL(a1):
|
|
||||||
if a1 is None:
|
|
||||||
raise ParseException(
|
|
||||||
"ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_NOT_IN(a1, a2):
|
|
||||||
if a1 in a2:
|
|
||||||
raise ParseException(
|
|
||||||
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ASSERT_IN(a1, a2):
|
|
||||||
if a1 not in a2:
|
|
||||||
raise ParseException(
|
|
||||||
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LinkerException(RiscemuBaseException):
|
|
||||||
def __init__(self, msg: str, data):
|
|
||||||
self.msg = msg
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_PARSE
|
|
||||||
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# MMU Exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryAccessException(RiscemuBaseException):
|
|
||||||
def __init__(self, msg: str, addr, size, op):
|
|
||||||
super(MemoryAccessException, self).__init__()
|
|
||||||
self.msg = msg
|
|
||||||
self.addr = addr
|
|
||||||
self.size = size
|
|
||||||
self.op = op
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_MEM
|
|
||||||
+ "{}(During {} at 0x{:08x} of size {}: {})".format(
|
|
||||||
self.__class__.__name__, self.op, self.addr, self.size, self.msg
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OutOfMemoryException(RiscemuBaseException):
|
|
||||||
def __init__(self, action):
|
|
||||||
self.action = action
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_MEM
|
|
||||||
+ "{}(Ran out of memory during {})".format(
|
|
||||||
self.__class__.__name__, self.action
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidAllocationException(RiscemuBaseException):
|
|
||||||
def __init__(self, msg, name, size, flags):
|
|
||||||
self.msg = msg
|
|
||||||
self.name = name
|
|
||||||
self.size = size
|
|
||||||
self.flags = flags
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return FMT_MEM + "{}[{}](name={}, size={}, flags={})".format(
|
|
||||||
self.__class__.__name__, self.msg, self.name, self.size, self.flags
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# CPU Exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class UnimplementedInstruction(RiscemuBaseException):
|
|
||||||
def __init__(self, ins: "Instruction", context=None):
|
|
||||||
self.ins = ins
|
|
||||||
self.context = context
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_CPU
|
|
||||||
+ "{}({}{})".format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
repr(self.ins),
|
|
||||||
", context={}".format(self.context) if self.context is not None else "",
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidRegisterException(RiscemuBaseException):
|
|
||||||
def __init__(self, reg):
|
|
||||||
self.reg = reg
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_CPU
|
|
||||||
+ "{}(Invalid register {})".format(self.__class__.__name__, self.reg)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidSyscallException(RiscemuBaseException):
|
|
||||||
def __init__(self, scall):
|
|
||||||
self.scall = scall
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return (
|
|
||||||
FMT_SYSCALL
|
|
||||||
+ "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def INS_NOT_IMPLEMENTED(ins):
|
|
||||||
raise UnimplementedInstruction(ins)
|
|
||||||
|
|
||||||
|
|
||||||
class NumberFormatException(RiscemuBaseException):
|
|
||||||
def __init__(self, msg):
|
|
||||||
super().__init__()
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
def message(self):
|
|
||||||
return "{}({})".format(self.__class__.__name__, self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
# this exception is not printed and simply signals that an interactive debugging session is
|
|
||||||
class LaunchDebuggerException(RiscemuBaseException):
|
|
||||||
def message(self) -> str:
|
|
||||||
return ""
|
|
@ -1,12 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class MemoryFlags:
|
|
||||||
read_only: bool
|
|
||||||
executable: bool
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "r{}{}".format(
|
|
||||||
"-" if self.read_only else "w", "x" if self.executable else "-"
|
|
||||||
)
|
|
@ -1,198 +0,0 @@
|
|||||||
import struct
|
|
||||||
from ctypes import c_float
|
|
||||||
from typing import Union, Any
|
|
||||||
|
|
||||||
bytes_t = bytes
|
|
||||||
|
|
||||||
|
|
||||||
class Float32:
|
|
||||||
__slots__ = ("_val",)
|
|
||||||
|
|
||||||
_val: c_float
|
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self) -> float:
|
|
||||||
"""
|
|
||||||
The value represented by this float
|
|
||||||
"""
|
|
||||||
return self._val.value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bytes(self) -> bytes:
|
|
||||||
"""
|
|
||||||
The values bit representation (as a bytes object)
|
|
||||||
"""
|
|
||||||
return struct.pack("<f", self.value)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bits(self) -> int:
|
|
||||||
"""
|
|
||||||
The values bit representation as an int (for easy bit manipulation)
|
|
||||||
"""
|
|
||||||
return int.from_bytes(self.bytes, byteorder="little")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_bytes(cls, val: Union[int, bytes_t, bytearray]):
|
|
||||||
if isinstance(val, int):
|
|
||||||
val = struct.unpack("!f", struct.pack("!I", val))[0]
|
|
||||||
return Float32(val)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, val: Union[float, c_float, "Float32", bytes_t, bytearray, int] = 0
|
|
||||||
):
|
|
||||||
if isinstance(val, (float, int)):
|
|
||||||
self._val = c_float(val)
|
|
||||||
elif isinstance(val, c_float):
|
|
||||||
self._val = c_float(val.value)
|
|
||||||
elif isinstance(val, (bytes, bytearray)):
|
|
||||||
self._val = c_float(struct.unpack("<f", val)[0])
|
|
||||||
elif isinstance(val, Float32):
|
|
||||||
self._val = val._val
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
"Unsupported value passed to Float32: {} ({})".format(
|
|
||||||
repr(val), type(val)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __add__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value + other)
|
|
||||||
|
|
||||||
def __sub__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value - other)
|
|
||||||
|
|
||||||
def __mul__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value * other)
|
|
||||||
|
|
||||||
def __truediv__(self, other: Any):
|
|
||||||
return self // other
|
|
||||||
|
|
||||||
def __floordiv__(self, other: Any):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value // other)
|
|
||||||
|
|
||||||
def __mod__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value % other)
|
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
|
||||||
if isinstance(other, (float, int)):
|
|
||||||
return self.value == other
|
|
||||||
elif isinstance(other, Float32):
|
|
||||||
return self.value == other.value
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __neg__(self):
|
|
||||||
return self.__class__(-self.value)
|
|
||||||
|
|
||||||
def __abs__(self):
|
|
||||||
return self.__class__(abs(self.value))
|
|
||||||
|
|
||||||
def __bytes__(self) -> bytes_t:
|
|
||||||
return self.bytes
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}({})".format(self.__class__.__name__, self.value)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.value)
|
|
||||||
|
|
||||||
def __format__(self, format_spec: str):
|
|
||||||
return self.value.__format__(format_spec)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.value)
|
|
||||||
|
|
||||||
def __gt__(self, other: Any):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.value > other
|
|
||||||
|
|
||||||
def __lt__(self, other: Any):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.value < other
|
|
||||||
|
|
||||||
def __le__(self, other: Any):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.value <= other
|
|
||||||
|
|
||||||
def __ge__(self, other: Any):
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.value
|
|
||||||
return self.value >= other
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.value)
|
|
||||||
|
|
||||||
def __pow__(self, power, modulo=None):
|
|
||||||
if modulo is not None:
|
|
||||||
raise ValueError("Float32 pow with modulo unsupported")
|
|
||||||
return self.__class__(self.value**power)
|
|
||||||
|
|
||||||
# right handed binary operators
|
|
||||||
|
|
||||||
def __radd__(self, other: Any):
|
|
||||||
return self + other
|
|
||||||
|
|
||||||
def __rsub__(self, other: Any):
|
|
||||||
return self.__class__(other) - self
|
|
||||||
|
|
||||||
def __rmul__(self, other: Any):
|
|
||||||
return self * other
|
|
||||||
|
|
||||||
def __rtruediv__(self, other: Any):
|
|
||||||
return self.__class__(other) // self
|
|
||||||
|
|
||||||
def __rfloordiv__(self, other: Any):
|
|
||||||
return self.__class__(other) // self
|
|
||||||
|
|
||||||
def __rmod__(self, other: Any):
|
|
||||||
return self.__class__(other) % self
|
|
||||||
|
|
||||||
def __rand__(self, other: Any):
|
|
||||||
return self.__class__(other) & self
|
|
||||||
|
|
||||||
def __ror__(self, other: Any):
|
|
||||||
return self.__class__(other) | self
|
|
||||||
|
|
||||||
def __rxor__(self, other: Any):
|
|
||||||
return self.__class__(other) ^ self
|
|
||||||
|
|
||||||
# bytewise operators:
|
|
||||||
|
|
||||||
def __and__(self, other: Union["Float32", float, int]):
|
|
||||||
if isinstance(other, float):
|
|
||||||
other = Float32(other)
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.bits
|
|
||||||
return self.from_bytes(self.bits & other)
|
|
||||||
|
|
||||||
def __or__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, float):
|
|
||||||
other = Float32(other)
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.bits
|
|
||||||
return self.from_bytes(self.bits | other)
|
|
||||||
|
|
||||||
def __xor__(self, other: Union["Float32", float]):
|
|
||||||
if isinstance(other, float):
|
|
||||||
other = Float32(other)
|
|
||||||
if isinstance(other, Float32):
|
|
||||||
other = other.bits
|
|
||||||
return self.from_bytes(self.bits ^ other)
|
|
||||||
|
|
||||||
def __lshift__(self, other: int):
|
|
||||||
return self.from_bytes(self.bits << other)
|
|
||||||
|
|
||||||
def __rshift__(self, other: int):
|
|
||||||
return self.from_bytes(self.bits >> other)
|
|
@ -1,31 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
class Instruction(ABC):
|
|
||||||
name: str
|
|
||||||
args: tuple
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_imm(self, num: int) -> int:
|
|
||||||
"""
|
|
||||||
parse and get immediate argument
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_imm_reg(self, num: int) -> Tuple[int, str]:
|
|
||||||
"""
|
|
||||||
parse and get an argument imm(reg)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_reg(self, num: int) -> str:
|
|
||||||
"""
|
|
||||||
parse and get an register argument
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{} {}".format(self.name, ", ".join(self.args))
|
|
@ -1,64 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
from .exceptions import ParseException
|
|
||||||
from ..types import T_AbsoluteAddress, T_RelativeAddress, NUMBER_SYMBOL_PATTERN
|
|
||||||
|
|
||||||
|
|
||||||
class InstructionContext:
|
|
||||||
base_address: T_AbsoluteAddress
|
|
||||||
"""
|
|
||||||
The address where the instruction block is placed
|
|
||||||
"""
|
|
||||||
|
|
||||||
labels: Dict[str, T_RelativeAddress]
|
|
||||||
"""
|
|
||||||
This dictionary maps all labels to their relative position of the instruction block
|
|
||||||
"""
|
|
||||||
|
|
||||||
numbered_labels: Dict[str, List[T_RelativeAddress]]
|
|
||||||
"""
|
|
||||||
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
|
|
||||||
the label was placed
|
|
||||||
"""
|
|
||||||
|
|
||||||
global_symbol_dict: Dict[str, T_AbsoluteAddress]
|
|
||||||
"""
|
|
||||||
A reference to the MMU's global symbol dictionary for access to global symbols
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.labels = dict()
|
|
||||||
self.numbered_labels = defaultdict(list)
|
|
||||||
self.base_address = 0
|
|
||||||
self.global_symbol_dict = dict()
|
|
||||||
|
|
||||||
def resolve_label(
|
|
||||||
self, symbol: str, address_at: Optional[T_RelativeAddress] = None
|
|
||||||
) -> Optional[T_AbsoluteAddress]:
|
|
||||||
if NUMBER_SYMBOL_PATTERN.match(symbol):
|
|
||||||
if address_at is None:
|
|
||||||
raise ParseException(
|
|
||||||
"Cannot resolve relative symbol {} without an address!".format(
|
|
||||||
symbol
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
direction = symbol[-1]
|
|
||||||
values = self.numbered_labels.get(symbol[:-1], [])
|
|
||||||
if direction == "b":
|
|
||||||
return max(
|
|
||||||
(addr + self.base_address for addr in values if addr < address_at),
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return min(
|
|
||||||
(addr + self.base_address for addr in values if addr > address_at),
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# if it's not a local symbol, try the globals
|
|
||||||
if symbol not in self.labels:
|
|
||||||
return self.global_symbol_dict.get(symbol, None)
|
|
||||||
# otherwise return the local symbol
|
|
||||||
return self.labels.get(symbol, None)
|
|
@ -1,54 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
MemorySection,
|
|
||||||
Instruction,
|
|
||||||
InstructionContext,
|
|
||||||
MemoryFlags,
|
|
||||||
T_RelativeAddress,
|
|
||||||
)
|
|
||||||
from .exceptions import MemoryAccessException
|
|
||||||
|
|
||||||
|
|
||||||
class InstructionMemorySection(MemorySection):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
instructions: List[Instruction],
|
|
||||||
name: str,
|
|
||||||
context: InstructionContext,
|
|
||||||
owner: str,
|
|
||||||
base: int = 0,
|
|
||||||
):
|
|
||||||
self.name = name
|
|
||||||
self.base = base
|
|
||||||
self.context = context
|
|
||||||
self.size = len(instructions) * 4
|
|
||||||
self.flags = MemoryFlags(True, True)
|
|
||||||
self.instructions = instructions
|
|
||||||
self.owner = owner
|
|
||||||
|
|
||||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Cannot read raw bytes from instruction section",
|
|
||||||
self.base + offset,
|
|
||||||
size,
|
|
||||||
"read",
|
|
||||||
)
|
|
||||||
|
|
||||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Cannot write raw bytes to instruction section",
|
|
||||||
self.base + offset,
|
|
||||||
size,
|
|
||||||
"write",
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
|
||||||
if offset % 4 != 0:
|
|
||||||
raise MemoryAccessException(
|
|
||||||
"Unaligned instruction fetch!",
|
|
||||||
self.base + offset,
|
|
||||||
4,
|
|
||||||
"instruction fetch",
|
|
||||||
)
|
|
||||||
return self.instructions[offset // 4]
|
|
@ -1,285 +0,0 @@
|
|||||||
from typing import Any, Union
|
|
||||||
from ctypes import c_int32, c_uint32
|
|
||||||
|
|
||||||
|
|
||||||
class Int32:
|
|
||||||
"""
|
|
||||||
This class implements 32bit signed integers (see :class:`UInt32` for unsigned integers)
|
|
||||||
|
|
||||||
It implements basically all mathematical dunder magic methods (__add__, __sub__, etc.)
|
|
||||||
|
|
||||||
You can use it just like you would any other integer, just be careful when passing it
|
|
||||||
to functions which actually expect an integer and not a Int32.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_type = c_int32
|
|
||||||
__slots__ = ("_val",)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0
|
|
||||||
):
|
|
||||||
if isinstance(val, (bytes, bytearray)):
|
|
||||||
signed = len(val) == 4 and self._type == c_int32
|
|
||||||
self._val = self.__class__._type(
|
|
||||||
int.from_bytes(val, "little", signed=signed)
|
|
||||||
)
|
|
||||||
elif isinstance(val, self.__class__._type):
|
|
||||||
self._val = val
|
|
||||||
elif isinstance(val, (c_uint32, c_int32, Int32)):
|
|
||||||
self._val = self.__class__._type(val.value)
|
|
||||||
elif isinstance(val, (int, bool)):
|
|
||||||
self._val = self.__class__._type(val)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Unknonw {} input type: {} ({})".format(
|
|
||||||
self.__class__.__name__, type(val), val
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __add__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
|
|
||||||
return self.__class__(self._val.value + other)
|
|
||||||
|
|
||||||
def __sub__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value - other)
|
|
||||||
|
|
||||||
def __mul__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value * other)
|
|
||||||
|
|
||||||
def __truediv__(self, other: Any):
|
|
||||||
return self // other
|
|
||||||
|
|
||||||
def __floordiv__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value // other)
|
|
||||||
|
|
||||||
def __mod__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value % other)
|
|
||||||
|
|
||||||
def __and__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value & other)
|
|
||||||
|
|
||||||
def __or__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value | other)
|
|
||||||
|
|
||||||
def __xor__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self._val.value ^ other)
|
|
||||||
|
|
||||||
def __lshift__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value << other)
|
|
||||||
|
|
||||||
def __rshift__(self, other: Union["Int32", int]):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.__class__(self.value >> other)
|
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
|
||||||
if isinstance(other, int):
|
|
||||||
return self.value == other
|
|
||||||
elif isinstance(other, Int32):
|
|
||||||
return self.value == other.value
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __neg__(self):
|
|
||||||
return self.__class__(-self._val.value)
|
|
||||||
|
|
||||||
def __abs__(self):
|
|
||||||
return self.__class__(abs(self.value))
|
|
||||||
|
|
||||||
def __bytes__(self):
|
|
||||||
return self.to_bytes(4)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}({})".format(self.__class__.__name__, self.value)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.value)
|
|
||||||
|
|
||||||
def __format__(self, format_spec: str):
|
|
||||||
return self.value.__format__(format_spec)
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.value)
|
|
||||||
|
|
||||||
def __gt__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.value > other
|
|
||||||
|
|
||||||
def __lt__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.value < other
|
|
||||||
|
|
||||||
def __le__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.value <= other
|
|
||||||
|
|
||||||
def __ge__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.value >= other
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.value)
|
|
||||||
|
|
||||||
def __cmp__(self, other: Any):
|
|
||||||
if isinstance(other, Int32):
|
|
||||||
other = other.value
|
|
||||||
return self.value.__cmp__(other)
|
|
||||||
|
|
||||||
# right handed binary operators
|
|
||||||
|
|
||||||
def __radd__(self, other: Any):
|
|
||||||
return self + other
|
|
||||||
|
|
||||||
def __rsub__(self, other: Any):
|
|
||||||
return self.__class__(other) - self
|
|
||||||
|
|
||||||
def __rmul__(self, other: Any):
|
|
||||||
return self * other
|
|
||||||
|
|
||||||
def __rtruediv__(self, other: Any):
|
|
||||||
return self.__class__(other) // self
|
|
||||||
|
|
||||||
def __rfloordiv__(self, other: Any):
|
|
||||||
return self.__class__(other) // self
|
|
||||||
|
|
||||||
def __rmod__(self, other: Any):
|
|
||||||
return self.__class__(other) % self
|
|
||||||
|
|
||||||
def __rand__(self, other: Any):
|
|
||||||
return self.__class__(other) & self
|
|
||||||
|
|
||||||
def __ror__(self, other: Any):
|
|
||||||
return self.__class__(other) | self
|
|
||||||
|
|
||||||
def __rxor__(self, other: Any):
|
|
||||||
return self.__class__(other) ^ self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self) -> int:
|
|
||||||
"""
|
|
||||||
The value represented by this Integer
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self._val.value
|
|
||||||
|
|
||||||
def unsigned(self) -> "UInt32":
|
|
||||||
"""
|
|
||||||
Convert to an unsigned representation. See :class:Uint32
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return UInt32(self)
|
|
||||||
|
|
||||||
def to_bytes(self, bytes: int = 4) -> bytearray:
|
|
||||||
"""
|
|
||||||
Convert to a bytearray of length :param:bytes
|
|
||||||
|
|
||||||
:param bytes: The length of the bytearray
|
|
||||||
:return: A little-endian representation of the contained integer
|
|
||||||
"""
|
|
||||||
return bytearray(self.unsigned_value.to_bytes(4, "little"))[0:bytes]
|
|
||||||
|
|
||||||
def signed(self) -> "Int32":
|
|
||||||
"""
|
|
||||||
Convert to a signed representation. See :class:Int32
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if self.__class__ == Int32:
|
|
||||||
return self
|
|
||||||
return Int32(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unsigned_value(self):
|
|
||||||
"""
|
|
||||||
Return the value interpreted as an unsigned integer
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return c_uint32(self.value).value
|
|
||||||
|
|
||||||
def shift_right_logical(self, ammount: Union["Int32", int]) -> "Int32":
|
|
||||||
"""
|
|
||||||
This function implements logical right shifts, meaning that the sign bit is shifted as well.
|
|
||||||
|
|
||||||
This is equivalent to (self.value % 0x100000000) >> ammount
|
|
||||||
|
|
||||||
:param ammount: Number of positions to shift
|
|
||||||
:return: A new Int32 object representing the shifted value (keeps the signed-ness of the source)
|
|
||||||
"""
|
|
||||||
if isinstance(ammount, Int32):
|
|
||||||
ammount = ammount.value
|
|
||||||
return self.__class__((self.value % 0x100000000) >> ammount)
|
|
||||||
|
|
||||||
def __int__(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def __hex__(self):
|
|
||||||
return hex(self.value)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def sign_extend(cls, data: Union[bytes, bytearray, int], bits: int):
|
|
||||||
"""
|
|
||||||
Create an instance of Int32 by sign extending :param:bits bits from :param:data
|
|
||||||
to 32 bits
|
|
||||||
|
|
||||||
:param data: The source data
|
|
||||||
:param bits: The number of bits in the source data
|
|
||||||
:return: An instance of Int32, holding the sign-extended value
|
|
||||||
"""
|
|
||||||
if isinstance(data, (bytes, bytearray)):
|
|
||||||
data = int.from_bytes(data, "little")
|
|
||||||
sign = data >> (bits - 1)
|
|
||||||
if sign > 1:
|
|
||||||
print("overflow in Int32.sext!")
|
|
||||||
if sign:
|
|
||||||
data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1)
|
|
||||||
return cls(data)
|
|
||||||
|
|
||||||
|
|
||||||
class UInt32(Int32):
|
|
||||||
"""
|
|
||||||
An unsigned version of :class:Int32.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_type = c_uint32
|
|
||||||
|
|
||||||
def unsigned(self) -> "UInt32":
|
|
||||||
"""
|
|
||||||
Return a new instance representing the same bytes, but signed
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unsigned_value(self) -> int:
|
|
||||||
return self._val.value
|
|
||||||
|
|
||||||
def shift_right_logical(self, ammount: Union["Int32", int]) -> "UInt32":
|
|
||||||
"""
|
|
||||||
see :meth:`Int32.shift_right_logical <Int32.shift_right_logical>`
|
|
||||||
|
|
||||||
:param ammount: Number of positions to shift
|
|
||||||
:return: A new Int32 object representing the shifted value (keeps the signed-ness of the source)
|
|
||||||
"""
|
|
||||||
if isinstance(ammount, Int32):
|
|
||||||
ammount = ammount.value
|
|
||||||
return UInt32(self.value >> ammount)
|
|
@ -1,173 +0,0 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR
|
|
||||||
from ..helpers import format_bytes
|
|
||||||
from . import (
|
|
||||||
MemoryFlags,
|
|
||||||
T_AbsoluteAddress,
|
|
||||||
InstructionContext,
|
|
||||||
T_RelativeAddress,
|
|
||||||
Instruction,
|
|
||||||
Int32,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MemorySection(ABC):
|
|
||||||
name: str
|
|
||||||
flags: MemoryFlags
|
|
||||||
size: int
|
|
||||||
base: T_AbsoluteAddress
|
|
||||||
owner: str
|
|
||||||
context: InstructionContext
|
|
||||||
|
|
||||||
@property
|
|
||||||
def end(self):
|
|
||||||
return self.base + self.size
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def dump(
|
|
||||||
self,
|
|
||||||
start: T_RelativeAddress,
|
|
||||||
end: Optional[T_RelativeAddress] = None,
|
|
||||||
fmt: Optional[str] = None,
|
|
||||||
bytes_per_row: Optional[int] = None,
|
|
||||||
rows: int = 10,
|
|
||||||
group: Optional[int] = None,
|
|
||||||
highlight: Optional[int] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Dump the section. If no end is given, the rows around start are printed and start is highlighted.
|
|
||||||
|
|
||||||
:param start: The address to start at
|
|
||||||
:param end: The end of the printed space
|
|
||||||
:param fmt: either hex, int, char or asm
|
|
||||||
:param bytes_per_row: the number of bytes displayed per row
|
|
||||||
:param rows: the number of rows displayed
|
|
||||||
:param group: Group this many bytes into one when displaying
|
|
||||||
:param highlight: Highlight the group containing this address
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if isinstance(start, Int32):
|
|
||||||
start = start.value
|
|
||||||
if isinstance(end, Int32):
|
|
||||||
end = end.value
|
|
||||||
|
|
||||||
if fmt is None:
|
|
||||||
if self.flags.executable and self.flags.read_only:
|
|
||||||
bytes_per_row = 4
|
|
||||||
fmt = "asm"
|
|
||||||
else:
|
|
||||||
fmt = "hex"
|
|
||||||
|
|
||||||
if fmt == "char":
|
|
||||||
if bytes_per_row is None:
|
|
||||||
bytes_per_row = 4
|
|
||||||
if group is None:
|
|
||||||
group = 1
|
|
||||||
|
|
||||||
if group is None:
|
|
||||||
group = 4
|
|
||||||
|
|
||||||
if bytes_per_row is None:
|
|
||||||
bytes_per_row = 4
|
|
||||||
|
|
||||||
if fmt not in ("asm", "hex", "int", "char"):
|
|
||||||
print(
|
|
||||||
FMT_ERROR
|
|
||||||
+ "[MemorySection] Unknown format {}, known formats are {}".format(
|
|
||||||
fmt, ", ".join(("asm", "hex", "int", "char"))
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
if end is None:
|
|
||||||
end = min(start + (bytes_per_row * (rows // 2)), self.size)
|
|
||||||
highlight = start
|
|
||||||
start = max(0, start - (bytes_per_row * (rows // 2)))
|
|
||||||
|
|
||||||
if fmt == "asm":
|
|
||||||
print(
|
|
||||||
FMT_MEM
|
|
||||||
+ "{}, viewing {} instructions:".format(self, (end - start) // 4)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
for addr in range(start, end, 4):
|
|
||||||
if addr == highlight:
|
|
||||||
print(FMT_UNDERLINE + FMT_ORANGE, end="")
|
|
||||||
print(
|
|
||||||
"0x{:04x}: {}{}".format(
|
|
||||||
self.base + addr, self.read_ins(addr), FMT_NONE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
FMT_MEM + "{}, viewing {} bytes:".format(self, (end - start)) + FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
aligned_end = (
|
|
||||||
end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
|
|
||||||
)
|
|
||||||
|
|
||||||
for addr in range(start, aligned_end, bytes_per_row):
|
|
||||||
hi_ind = (highlight - addr) // group if highlight is not None else -1
|
|
||||||
print(
|
|
||||||
"0x{:04x}: {}{}".format(
|
|
||||||
self.base + addr,
|
|
||||||
format_bytes(
|
|
||||||
self.read(addr, bytes_per_row), fmt, group, hi_ind
|
|
||||||
),
|
|
||||||
FMT_NONE,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if aligned_end != end:
|
|
||||||
hi_ind = (
|
|
||||||
(highlight - aligned_end) // group if highlight is not None else -1
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"0x{:04x}: {}{}".format(
|
|
||||||
self.base + aligned_end,
|
|
||||||
format_bytes(
|
|
||||||
self.read(aligned_end, end % bytes_per_row),
|
|
||||||
fmt,
|
|
||||||
group,
|
|
||||||
hi_ind,
|
|
||||||
),
|
|
||||||
FMT_NONE,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def dump_all(
|
|
||||||
self,
|
|
||||||
fmt: Optional[str] = None,
|
|
||||||
bytes_per_row: Optional[int] = None,
|
|
||||||
rows: int = 10,
|
|
||||||
group: Optional[int] = None,
|
|
||||||
highlight: Optional[int] = None,
|
|
||||||
):
|
|
||||||
self.dump(0, self.size, fmt, bytes_per_row, rows, group, highlight)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.name,
|
|
||||||
self.base,
|
|
||||||
self.size,
|
|
||||||
self.flags,
|
|
||||||
self.owner,
|
|
||||||
)
|
|
@ -1,117 +0,0 @@
|
|||||||
from typing import List, Optional, Set
|
|
||||||
|
|
||||||
from ..colors import FMT_RED, FMT_BOLD, FMT_NONE, FMT_MEM
|
|
||||||
from ..helpers import get_section_base_name
|
|
||||||
from . import InstructionContext, T_AbsoluteAddress, MemorySection
|
|
||||||
|
|
||||||
|
|
||||||
class Program:
|
|
||||||
"""
|
|
||||||
This represents a collection of sections which together form an executable program
|
|
||||||
|
|
||||||
When you want to create a program which can be located anywhere in memory, set base to None,
|
|
||||||
this signals the other components, that this is relocatable. Set the base of each section to
|
|
||||||
the offset in the program, and everything will be taken care of for you.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
context: InstructionContext
|
|
||||||
global_labels: Set[str]
|
|
||||||
relative_labels: Set[str]
|
|
||||||
sections: List[MemorySection]
|
|
||||||
base: Optional[T_AbsoluteAddress]
|
|
||||||
is_loaded: bool
|
|
||||||
|
|
||||||
@property
|
|
||||||
def size(self):
|
|
||||||
if len(self.sections) == 0:
|
|
||||||
return 0
|
|
||||||
if self.base is None:
|
|
||||||
return self.sections[-1].base + self.sections[-1].size
|
|
||||||
return (self.sections[-1].base - self.base) + self.sections[-1].size
|
|
||||||
|
|
||||||
def __init__(self, name: str, base: Optional[int] = None):
|
|
||||||
self.name = name
|
|
||||||
self.context = InstructionContext()
|
|
||||||
self.sections = []
|
|
||||||
self.global_labels = set()
|
|
||||||
self.relative_labels = set()
|
|
||||||
self.base = base
|
|
||||||
self.is_loaded = False
|
|
||||||
|
|
||||||
def add_section(self, sec: MemorySection):
|
|
||||||
# print a warning when a section is located before the programs base
|
|
||||||
if self.base is not None:
|
|
||||||
if sec.base < self.base:
|
|
||||||
print(
|
|
||||||
FMT_RED
|
|
||||||
+ FMT_BOLD
|
|
||||||
+ "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
|
|
||||||
sec, self.name, self.base
|
|
||||||
)
|
|
||||||
+ FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
self.sections.append(sec)
|
|
||||||
# keep section list ordered
|
|
||||||
self.sections.sort(key=lambda section: section.base)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "{}(name={},sections={},base={})".format(
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.name,
|
|
||||||
self.global_labels,
|
|
||||||
[s.name for s in self.sections],
|
|
||||||
self.base,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def entrypoint(self):
|
|
||||||
if "_start" in self.context.labels:
|
|
||||||
return self.context.labels.get("_start")
|
|
||||||
if "main" in self.context.labels:
|
|
||||||
return self.context.labels.get("main")
|
|
||||||
for sec in self.sections:
|
|
||||||
if get_section_base_name(sec.name) == ".text" and sec.flags.executable:
|
|
||||||
return sec.base
|
|
||||||
|
|
||||||
def loaded_trigger(self, at_addr: T_AbsoluteAddress):
|
|
||||||
"""
|
|
||||||
This trigger is called when the binary is loaded and its final address in memory is determined
|
|
||||||
|
|
||||||
This will do a small sanity check to prevent programs loading twice, or at addresses they don't
|
|
||||||
expect to be loaded.
|
|
||||||
|
|
||||||
Then it will finalize all relative symbols defined in it to point to the correct addresses.
|
|
||||||
|
|
||||||
:param at_addr: the address where the program will be located
|
|
||||||
"""
|
|
||||||
if self.is_loaded:
|
|
||||||
if at_addr != self.base:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Program loaded twice at different addresses! This will probably break things!"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.base is not None and self.base != at_addr:
|
|
||||||
print(
|
|
||||||
FMT_MEM
|
|
||||||
+ "WARNING: Program loaded at different address then expected! (loaded at {}, "
|
|
||||||
"but expects to be loaded at {})".format(at_addr, self.base) + FMT_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
# check if we are relocating
|
|
||||||
if self.base != at_addr:
|
|
||||||
offset = at_addr if self.base is None else at_addr - self.base
|
|
||||||
|
|
||||||
# move all sections by the offset
|
|
||||||
for sec in self.sections:
|
|
||||||
sec.base += offset
|
|
||||||
|
|
||||||
# move all relative symbols by the offset
|
|
||||||
for name in self.relative_labels:
|
|
||||||
self.context.labels[name] += offset
|
|
||||||
|
|
||||||
self.base = at_addr
|
|
||||||
self.context.base_address = at_addr
|
|
@ -1,58 +0,0 @@
|
|||||||
import os
|
|
||||||
from abc import abstractmethod, ABC
|
|
||||||
from typing import Union, Iterator, List
|
|
||||||
|
|
||||||
from . import T_ParserOpts, Program
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramLoader(ABC):
|
|
||||||
"""
|
|
||||||
A program loader is always specific to a given source file. It is a place to store all state
|
|
||||||
concerning the parsing and loading of that specific source file, including options.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, source_path: str, options: T_ParserOpts):
|
|
||||||
self.source_path = source_path
|
|
||||||
self.options = options
|
|
||||||
self.filename = os.path.split(self.source_path)[-1]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def can_parse(cls, source_path: str) -> float:
|
|
||||||
"""
|
|
||||||
Return confidence that the file located at source_path
|
|
||||||
should be parsed and loaded by this loader
|
|
||||||
:param source_path: the path of the source file
|
|
||||||
:return: the confidence that this file belongs to this parser
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]:
|
|
||||||
"""
|
|
||||||
parse command line args into an options dictionary
|
|
||||||
|
|
||||||
:param argv: the command line args list
|
|
||||||
:return: all remaining command line args and the parser options object
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader":
|
|
||||||
"""
|
|
||||||
Instantiate a loader for the given source file with the required arguments
|
|
||||||
|
|
||||||
:param source_path: the path to the source file
|
|
||||||
:param options: the parsed options (guaranteed to come from this classes get_options method.
|
|
||||||
:return: An instance of a ProgramLoader for the spcified source
|
|
||||||
"""
|
|
||||||
return cls(source_path, options)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def parse(self) -> Union[Program, Iterator[Program]]:
|
|
||||||
"""
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
pass
|
|
@ -1,30 +0,0 @@
|
|||||||
from typing import Union, Tuple
|
|
||||||
|
|
||||||
from . import Instruction, T_RelativeAddress, InstructionContext
|
|
||||||
from ..helpers import parse_numeric_argument
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleInstruction(Instruction):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
|
|
||||||
context: InstructionContext,
|
|
||||||
addr: T_RelativeAddress,
|
|
||||||
):
|
|
||||||
self.context = context
|
|
||||||
self.name = name
|
|
||||||
self.args = args
|
|
||||||
self.addr = addr
|
|
||||||
|
|
||||||
def get_imm(self, num: int) -> int:
|
|
||||||
resolved_label = self.context.resolve_label(self.args[num], self.addr)
|
|
||||||
if resolved_label is None:
|
|
||||||
return parse_numeric_argument(self.args[num])
|
|
||||||
return resolved_label
|
|
||||||
|
|
||||||
def get_imm_reg(self, num: int) -> Tuple[int, str]:
|
|
||||||
return self.get_imm(num + 1), self.get_reg(num)
|
|
||||||
|
|
||||||
def get_reg(self, num: int) -> str:
|
|
||||||
return self.args[num]
|
|
@ -1,44 +0,0 @@
|
|||||||
import setuptools
|
|
||||||
from glob import glob
|
|
||||||
|
|
||||||
import riscemu
|
|
||||||
|
|
||||||
with open("README.md", "r", encoding="utf-8") as fh:
|
|
||||||
long_description = fh.read()
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
name="riscemu",
|
|
||||||
version=riscemu.__version__,
|
|
||||||
author=riscemu.__author__,
|
|
||||||
author_email="pip@antonlydike.de",
|
|
||||||
description="RISC-V userspace and machine mode emulator",
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
url="https://github.com/antonlydike/riscemu",
|
|
||||||
project_urls={
|
|
||||||
"Bug Tracker": "https://github.com/AntonLydike/riscemu/issues",
|
|
||||||
},
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
],
|
|
||||||
package_dir={"": "."},
|
|
||||||
packages=[
|
|
||||||
"riscemu",
|
|
||||||
"riscemu.decoder",
|
|
||||||
"riscemu.instructions",
|
|
||||||
"riscemu.IO",
|
|
||||||
"riscemu.priv",
|
|
||||||
"riscemu.types",
|
|
||||||
],
|
|
||||||
package_data={
|
|
||||||
"riscemu": ["libc/*.s", "py.typed"],
|
|
||||||
},
|
|
||||||
data_files=[
|
|
||||||
("libc", glob("libc/*.s")),
|
|
||||||
],
|
|
||||||
scripts=["riscemu/tools/riscemu"],
|
|
||||||
python_requires=">=3.8",
|
|
||||||
install_requires=["pyelftools~=0.27"],
|
|
||||||
)
|
|
@ -1,3 +0,0 @@
|
|||||||
from .test_tokenizer import *
|
|
||||||
from .test_helpers import *
|
|
||||||
from .test_integers import *
|
|
@ -1,2 +0,0 @@
|
|||||||
.lit_test_times.txt
|
|
||||||
Output
|
|
@ -1,24 +0,0 @@
|
|||||||
// RUN: python3 -m riscemu -v -o ignore_exit_code %s | filecheck %s
|
|
||||||
.data
|
|
||||||
fibs: .space 1024
|
|
||||||
|
|
||||||
.text
|
|
||||||
main:
|
|
||||||
addi s1, zero, 0 // storage index
|
|
||||||
addi s2, zero, 1024 // last storage index
|
|
||||||
addi t0, zero, 1 // t0 = F_{i}
|
|
||||||
addi t1, zero, 1 // t1 = F_{i+1}
|
|
||||||
loop:
|
|
||||||
sw t0, fibs(s1) // save
|
|
||||||
add t2, t1, t0 // t2 = F_{i+2}
|
|
||||||
addi t0, t1, 0 // t0 = t1
|
|
||||||
addi t1, t2, 0 // t1 = t2
|
|
||||||
addi s1, s1, 4 // increment storage pointer
|
|
||||||
blt s1, s2, loop // loop as long as we did not reach array length
|
|
||||||
ebreak
|
|
||||||
// exit gracefully
|
|
||||||
add a0, zero, t2
|
|
||||||
addi a7, zero, 93
|
|
||||||
scall // exit with code fibs(n) & 2^32
|
|
||||||
|
|
||||||
// CHECK: [CPU] Program exited with code 1265227608
|
|
@ -1,15 +0,0 @@
|
|||||||
// RUN: python3 -m riscemu -v %s | filecheck %s
|
|
||||||
.data
|
|
||||||
msg: .ascii "Hello world\n"
|
|
||||||
.text
|
|
||||||
addi a0, zero, 1 // print to stdout
|
|
||||||
addi a1, zero, msg // load msg address
|
|
||||||
addi a2, zero, 12 // write 12 bytes
|
|
||||||
addi a7, zero, SCALL_WRITE // write syscall code
|
|
||||||
scall
|
|
||||||
addi a0, zero, 0 // set exit code to 0
|
|
||||||
addi a7, zero, SCALL_EXIT // exit syscall code
|
|
||||||
scall
|
|
||||||
|
|
||||||
// CHECK: Hello world
|
|
||||||
// CHECK: [CPU] Program exited with code 0
|
|
@ -1,114 +0,0 @@
|
|||||||
// RUN: python3 -m riscemu -v %s -o libc | filecheck %s
|
|
||||||
|
|
||||||
.data
|
|
||||||
|
|
||||||
data:
|
|
||||||
.byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
|
|
||||||
.byte 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
|
|
||||||
|
|
||||||
dest:
|
|
||||||
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
|
|
||||||
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
|
|
||||||
|
|
||||||
small_str:
|
|
||||||
.string "test"
|
|
||||||
|
|
||||||
.text
|
|
||||||
|
|
||||||
.globl main
|
|
||||||
main:
|
|
||||||
// test that strlen(data) == 15
|
|
||||||
addi sp, sp, -4
|
|
||||||
sw ra, 0(sp)
|
|
||||||
la a0, data
|
|
||||||
// call strlen(data)
|
|
||||||
jal strlen
|
|
||||||
li t0, 15
|
|
||||||
bne a0, t0, _fail
|
|
||||||
|
|
||||||
// now memcpy strlen(data)+1 bytes from data to dest
|
|
||||||
la a0, dest
|
|
||||||
la a1, data
|
|
||||||
li a2, 16
|
|
||||||
// call strncpy(dest, data, 16)
|
|
||||||
jal strncpy
|
|
||||||
la a1, dest
|
|
||||||
// fail because strncpy should return pointer to dest
|
|
||||||
bne a0, a1, _fail
|
|
||||||
// check that dest and data are the same
|
|
||||||
jal check_data_dest_is_same
|
|
||||||
la a0, dest
|
|
||||||
li a1, 0x11
|
|
||||||
li a2, 16
|
|
||||||
// test that memset(dest) workds
|
|
||||||
// call memset(dest, 0x11, 16)
|
|
||||||
jal memset
|
|
||||||
// check that all of dest is 0x11111111
|
|
||||||
li t1, 0x11111111
|
|
||||||
la a0, dest
|
|
||||||
lw t0, 0(a0)
|
|
||||||
bne t0, t1, _fail
|
|
||||||
lw t0, 1(a0)
|
|
||||||
bne t0, t1, _fail
|
|
||||||
lw t0, 2(a0)
|
|
||||||
bne t0, t1, _fail
|
|
||||||
lw t0, 3(a0)
|
|
||||||
bne t0, t1, _fail
|
|
||||||
// test memchr
|
|
||||||
// test memchr
|
|
||||||
la a0, data
|
|
||||||
li a1, 0x55
|
|
||||||
li a2, 16
|
|
||||||
// memchr(data, 0x55, 16)
|
|
||||||
jal memchr
|
|
||||||
la t0, data
|
|
||||||
addi t0, t0, 4
|
|
||||||
// fail if a0 != data+4
|
|
||||||
bne a0, t0, _fail
|
|
||||||
la a0, data
|
|
||||||
li a1, 0x12
|
|
||||||
li a2, 16
|
|
||||||
// memchr(data, 0x12, 16)
|
|
||||||
jal memchr
|
|
||||||
// check that result is NULL
|
|
||||||
bne a0, zero, _fail
|
|
||||||
// test strcpy
|
|
||||||
la a0, dest
|
|
||||||
la a1, small_str
|
|
||||||
// call strcpy(dest, small_str)
|
|
||||||
jal strcpy
|
|
||||||
la t0, dest
|
|
||||||
lw t1, 0(a0)
|
|
||||||
// ascii for "tset", as risc-v is little endian
|
|
||||||
li t2, 0x74736574
|
|
||||||
bne t1, t2, _fail
|
|
||||||
|
|
||||||
// return to exit() wrapper
|
|
||||||
lw ra, 0(sp)
|
|
||||||
addi sp, sp, 4
|
|
||||||
li a0, 0
|
|
||||||
ret
|
|
||||||
|
|
||||||
_fail:
|
|
||||||
ebreak
|
|
||||||
// fail the test run
|
|
||||||
li a0, -1
|
|
||||||
jal exit
|
|
||||||
|
|
||||||
|
|
||||||
check_data_dest_is_same:
|
|
||||||
la a0, data
|
|
||||||
la a1, dest
|
|
||||||
li a2, 4
|
|
||||||
1:
|
|
||||||
lw t0, 0(a0)
|
|
||||||
lw t1, 0(a1)
|
|
||||||
bne t0, t1, _fail
|
|
||||||
addi a0, a0, 4
|
|
||||||
addi a1, a1, 4
|
|
||||||
addi a2, a2, -1
|
|
||||||
blt zero, a2, 1b
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
//CHECK: [CPU] Program exited with code 0
|
|
@ -1,9 +0,0 @@
|
|||||||
import lit.formats
|
|
||||||
import os
|
|
||||||
|
|
||||||
config.test_source_root = os.path.dirname(__file__)
|
|
||||||
xdsl_src = os.path.dirname(os.path.dirname(config.test_source_root))
|
|
||||||
|
|
||||||
config.name = "riscemu"
|
|
||||||
config.test_format = lit.formats.ShTest(preamble_commands=[f"cd {xdsl_src}"])
|
|
||||||
config.suffixes = ['.asm', '.s']
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue