diff --git a/.github/workflows/md_lint.yml b/.github/workflows/md_lint.yml new file mode 100644 index 00000000..489008e3 --- /dev/null +++ b/.github/workflows/md_lint.yml @@ -0,0 +1,17 @@ +name: Lint markdown files + +# Build on every branch push, tag push, and pull request change: +on: [push, pull_request] + +jobs: + checks: + runs-on: ubuntu-latest + name: Lint md files + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Lint markdown files + uses: articulate/actions-markdownlint@v1.1.0 + with: + config: .markdownlint.jsonc diff --git a/.gitignore b/.gitignore index eb3b4024..86ce3910 100644 --- a/.gitignore +++ b/.gitignore @@ -211,7 +211,8 @@ dmypy.json cython_debug/ -.vscode/ +.vscode/* +!.vscode/extensions.json # Generated by Cargo # will have compiled files and executables diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 5883ccdf..00000000 --- a/.markdownlint.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "MD029": false, - "MD013": { "code_block_line_length": 120} -} \ No newline at end of file diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 00000000..ef8db42f --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,14 @@ +{ + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md + // MD024 - Multiple headings with the same content + "MD024": { + "siblings_only": true + }, + + // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md + // MD013 - Line length + "MD013": { + "code_block_line_length": 120, + "headings": false + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..a20a77e4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "davidanson.vscode-markdownlint" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..6b531f31 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,508 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.7.10] - 2023-09-24 + +### Added + +- Add Python binding for `rabbitizer.__version_info__` +- Add Python binding for `rabbitizer.__version__` +- Add Python binding for `rabbitizer.__author__` +- Add `CHANGELOG.md` +- Add markdown linter to CI + +### Fixed + +- Fix passing `None` to third argument of `RegistersTracker.processLui` on the + Python bindings +- Fix passing `None` to first argument of `RegistersTracker` on the Python bindings + +## [1.7.9] - 2023-09-18 + +### Changed + +- Don't attempt disassembling R5900's `vclipw` / `vsqrt` in gnu mode (#44) + - Thanks @ethteck + +## [1.7.8] - 2023-09-13 + +### Changed + +- Consider `syscall` as an instruction that isn't emited by compilers +- Add `$` to special PS2 regs (#41) + - Thanks @ethteck +- The `treatJAsUnconditionalBranch` option is no longer deprecated + +### Fixed + +- Fix size calculation required for the buffer of the disassembled instruction + for a few edge cases +- Properly implement r5900's `vcallms` operands + +## [1.7.7] - 2023-08-27 + +### Changed + +- Reimplement hash function for Enum type. + +### Fixed + +- Fix type hints for Enum type missing the hash function. + +## [1.7.6] - 2023-08-22 + +### Fixed + +- Fix syscall having two arguments instead of just one + +## [1.7.5] - 2023-08-08 + +### Changed + +- Catch more errors and raise Python exceptions on them in the instruction + disassembly binding + +### Fixed + +- Fix bug where the buffer size for a decoded instruction would be computed + wrongly if the passed `extraLJust` parameter plus the global `opcodeLJust` + option resulted in a negative value. + +## [1.7.4] - 2023-06-13 + +### Added + +- Provide macros to allow using gte instructions in both assembly and C. Those + are provided under the [`docs/r3000gte`](./docs/r3000gte/) folder of the repository + +### Fixed + +- Fix R5900 vf registers not using the `$vf` prefix in numeric register mode. + +## [1.7.3] - 2023-06-10 + +### Fixed + +- Fix Rust release + +## [1.7.2] - 2023-05-04 + +### Added + +- Add `InstrIdType` +- Add `clippy` rust linter and fix the new warnings + +### Changed + +- Move all the tables and templates into the `tables/` folder +- Format and cleanup rust code + +## [1.7.1] - 2023-05-02 + +### Changed + +- General cleanups in the codebase. Not actual features or fixes were made + +## [1.7.0] - 2023-04-30 + +### Added + +- Support for R3000 GTE instructions (a.k.a. the PS1 specific instructions) + - PR #31 +- Add CI for checking the repo is always formatted +- Add CI for checking the tables have been regenerated + +### Changed + +- Only run the CI that builds the python bindings for every machine on new releases. + - This action was by far the slowest, so this should speed up PRs +- Other Github Actions cleanups + +## [1.6.2] - 2023-04-27 + +### Fixed + +- Fix `getInstrIndexAsVram` taking the upper 8 bits instead of 4 bits for the + vram calculation + +## [1.6.1] - 2023-04-18 + +### Added + +- Expose `fs`, `ft` and `fd` registers to Python bindings. +- Add enums for `RegCop1O32`, `RegCop1N32` and `RegCop1N64` registers for + Python bindings + +### Fixed + +- Fix `Instruction`'s `vram` parameter not initialized if it was not passed to + the constructor on the Python binding. + +## [1.6.0] - 2023-04-17 + +### Added + +- Adds `gnuMode` to the configuration. + - Toggles various tweaks to allow building and matching with GNU `as`, which + original compiler will not like. + - Enabled by default. + - Turning this mode off makes the `div $0,` pseudo instruction to not be used + +### Changed + +- Removed special treatment for R5900's `trunc.w.s`. + - `cvt.w.s` and `trunc.w.s` will be decoded as-is when `gnuMode` is turned off. + - If `gnuMode` is turned on this instruction those two instructions are + decoded as `.word`s +- R5900's special operands `I`, `Q`, `R` and `ACC` will not longer be decoded + with a `$` prefix +- All autogenerated files are added the `linguist-generated` attribute. + +## [1.5.11] - 2023-04-02 + +- Fake version bump to convince CI to build binaries for Python 3.11 +- Updates some Github Actions to newer versions + +## [1.5.10] - 2023-02-23 + +### Added + +- Add a unit test to ensure every bindings use the same version number + - This is enforced in CI + +## [1.5.9] - 2023-02-23 + +### Added + +- Implement `mfc2`, `mtc2`, `cfc2` and `ctc2` +- Document C api usage + +### Changed + +- Expand list of instructions not emitted by C compilers + +### Fixed + +- Fix "emitted" typo + +## [1.5.8] - 2022-12-20 + +### Added + +- New `modifiesF*` and `readsF*`properties in the InstructionDescriptor + +### Changed + +- Column limit for C files has been changed to 120 + +### Fixed + +- Fix source distribution of Python package + +## [1.5.7] - 2022-12-20 + +### Fixed + +- Fix a Rust binding not taking `&` + +## [1.5.6] - 2022-12-19 + +### Added + +- Introduce `RegisterDescriptor` +- `outputsToGprZero` method + +### Removed + +- `jalr_rd` has been removed. Its old conditional behavior is now handled with + the new `cpu_maybe_rd_rs` operand + +### Fixed + +- Fix some typos in Rust bindings + +## [1.5.5] - 2022-12-19 + +### Changed + +- Change the global option `treatJAsUnconditionalBranch`'s default to `true` +- Cleanup Rust bindings to reduce useless indirections + +### Fixed + +- Actually package the C files in the Rust crate + +## [1.5.4] - 2022-12-18 + +### Fixed + +- Why Are We Still Here? Just To Suffer[?](https://knowyourmeme.com/memes/why-are-we-still-here-just-to-suffer) + - Try to fix Rust bindings packaging + +## [1.5.3] - 2022-12-18 + +### Fixed + +- Trying to fix crate publishing, again again + +## [1.5.2] - 2022-12-18 + +### Fixed + +- Try to fix automatic Rust crate publishing again + +## [1.5.1] - 2022-12-18 + +### Fixed + +- Attempt to fix Rust crate publishing + +## [1.5.0] - 2022-12-18 + +1.5.0: Rust bindings + +### Added + +- Adds Rust bindings +- The table-macro-hell has been changed a little to include pre-processed + versions of all the tables in the repository. This change should be more + friendly to IDEs + +## [1.4.0] - 2022-12-16 + +### Added + +- New methods to know if an instruction reads the value of a GPR: `.readsRs`, + `.readsRd` and `.readsRt` +- Descriptor logic errors are now checked in CI + +### Changed + +- A lot of descriptor information where corrected + +### Deprecated + +- Deprecate `isJrRa()`, prefer new `isReturn()` method +- Deprecate `isJrNotRa()`, prefer new `isJumptableJump()` method + +### Removed + +- 3 non-existent RSP instructions where removed (`rsp_cache`, `rsp_lwc1` and + `rsp_swc1`) +- Remove `setup.cfg` and move all the info to `pyproject.toml` + +## [1.3.3] - 2022-11-30 + +### Fixed + +- Fix RSP's jalr being marked as invalid + - Thanks to @Mr-Wiseguy for noticing + +## [1.3.2] - 2022-11-30 + +### Changed + +- Check for `_INVALID` unique ids in `isValid` + +### Fixed + +- Fixed RSP using CPU cop0 .inc file instead of RSP cop0 and cop2 files (#13) + - Thanks to @Mr-Wiseguy + +## [1.3.1] - 2022-10-16 + +### Changed + +- Removes signedness from `AccessType` + +## [1.3.0] - 2022-10-15 + +### Added + +- Adds C++ bindings +- New functions/methods: + - `Instruction#getBranchOffsetGeneric()`: Like + `Instruction#getGenericBranchOffset()`, but does not require the + `currentVram` parameter + - `Instruction#getBranchVramGeneric()` + - `Instruction#getDestinationGpr()` + - `Instruction#hasOperandAlias()` + - `Instruction#isJumpWithAddress()` + - `Instruction#readsHI()` + - `Instruction#readsLO()` + - `Instruction#modifiesHI()` + - `Instruction#modifiesLO()` + - `Instruction#getAccessType()` + - `InstrCategory#fromStr()` +- New enums: + - `OperandType` + - `AccessType` + +### Deprecated + +- Deprecated functions/methods: + - `Instruction#getImmediate()`: Use `Instruction#getProcessedImmediate()` instead + - `Instruction#getGenericBranchOffset()`: Use + `Instruction#getBranchOffsetGeneric()` instead + - `Instruction#mapInstrToType()`: Use `Instruction#getAccessType()` instead + - `Instruction#isUnknownType()` + - `Instruction#isJType()`: Use `Instruction#isJumpWithAddress()` instead + - `Instruction#isIType()`: Use + `Instruction#hasOperandAlias(OperandType.cpu_immediate)` instead + - `Instruction#isRType()` + - `Instruction#isRegimmType()` + +### Fixed + +- Fix missing operands on some trap instructions +- Fix buffer size calculation for disassembly + +## [1.2.2] - 2022-10-09 + +1.2.2: `bal` and proper `\\` escape + +### Added + +- Add `bal` support + +### Changed + +- Use special notation for branches which should produce matching instructions + even when no `immOverride` was passed +- Escape `\` -> `\\` on `RabbitizerUtils_escapeString` +- Add `extern "C"` in every header + +## [1.2.1] - 2022-09-26 + +1.2.1: Static library building in the Makefile + +### Added + +- Makefile now creates a `librabbitizer.a` file by default. +- Makefile can also build a `librabbitizer.so` with `make dynamic` +- New `include/rabbitizer.h` header which includes every other header +- Added a version header + +## [1.2.0] - 2022-09-17 + +1.2.0: `%got` compatibility + +### Added + +- New methods in `RegistersTracker` to support tracking `%got` accesses + - `processGpLoad`, which works similar to `processLui` + - `preprocessLoAndGetInfo`, which replaces the now deprecated `getLuiOffsetForLo` + +### Changed + +- Move operand types to the table format. Operands were also renamed to a more + concise name. + +### Fixed + +- Fix the wrong returned value on `Utils.From2Complement` when `bits` equals 32. + +## [1.1.0] - 2022-08-27 + +1.1.0: RSP and R5900 support + +### Added + +- Add proper instruction decoding for N64's RSP +- Add support for decoding the R5900 processor (PS2's Emotion Engine processor) + +### Changed + +- Cleanup internal instruction tables format + +## [1.0.1] - 2022-07-12 + +### Added + +- New function `Utils.escapeString`: Escapes escape characters. + +### Changed + +- Allow taking `None` in `Abi.fromStr`: returns `o32` in that case. +- Many cleanups and formats + +## [1.0.0] - 2022-07-07 + +### Uncategorized + +### Added + +- New classes: + - `RegistersTracker`: Intended to facilitate tracking the state of the general + purpose registers. +- New enums: + - `RegGprO32` and `RegGprN32` +- New configurations: + - `misc_omit0XOnSmallImm`: If `True` then the leading `0x` of immediates in + the [-9, 9] range is omitted. Defaults to `False`. + - `misc_upperCaseImm`: If `True` then immediates are outputted in uppercase. + Defaults to `True`. +- Many code cleanups + +### Changed + +- `Instruction` changes: + - Constructor can accept `vram` and `category` parameters. + - `isHiPair` renamed to `canBeHi`. + - `isLoPair` renamed to `canBeLo`. + - Added `doesLoad`, `doesStore`, `maybeIsMove`, `isPseudo` and + `architectureVersion` to `InstrDescriptor`. + - Python API: `Instruction#rs`, `Instruction#rt` and ``Instruction#rd` now + return an enum gpr type and will raise an exception if the instruction does + not reference the corresponding register. + - New methods: `getOpcodeName`, `getProcessedImmediate`, `hasDelaySlot` and `isValid`. + - Added `__reduce__` method to allow pickling the object. + +## [0.1.0] - 2022-06-10 + +- First version + +[unreleased]: https://github.com/Decompollaborate/rabbitizer/compare/master...develop +[1.7.9]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.8...1.7.9 +[1.7.8]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.7...1.7.8 +[1.7.7]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.6...1.7.7 +[1.7.6]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.5...1.7.6 +[1.7.5]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.4...1.7.5 +[1.7.4]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.3...1.7.4 +[1.7.3]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.2...1.7.3 +[1.7.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.1...1.7.2 +[1.7.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.7.0...1.7.1 +[1.7.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.6.2...1.7.0 +[1.6.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.6.1...1.6.2 +[1.6.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.6.0...1.6.1 +[1.6.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.11...1.6.0 +[1.5.11]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.10...1.5.11 +[1.5.10]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.9...1.5.10 +[1.5.9]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.8...1.5.9 +[1.5.8]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.7...1.5.8 +[1.5.7]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.6...1.5.7 +[1.5.6]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.5...1.5.6 +[1.5.5]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.4...1.5.5 +[1.5.4]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.3...1.5.4 +[1.5.3]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.2...1.5.3 +[1.5.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.4.0...1.5.0 +[1.4.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.3.3...1.4.0 +[1.3.3]: https://github.com/Decompollaborate/rabbitizer/compare/1.3.2...1.3.3 +[1.3.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.3.1...1.3.2 +[1.3.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.2.2...1.3.0 +[1.2.2]: https://github.com/Decompollaborate/rabbitizer/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.0.1...1.1.0 +[1.0.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/Decompollaborate/rabbitizer/compare/0.1.0...1.0.0 +[0.1.0]: https://github.com/Decompollaborate/rabbitizer/releases/tag/0.1.0 diff --git a/Cargo.toml b/Cargo.toml index 469f223d..a6becc84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "rabbitizer" # Version should be synced with include/common/RabbitizerVersion.h -version = "1.7.9" +version = "1.7.10" edition = "2021" authors = ["Anghelo Carvajal "] description = "MIPS instruction decoder" diff --git a/docs/usage_c_api.md b/docs/usage_c_api.md index 9df15037..33c21514 100644 --- a/docs/usage_c_api.md +++ b/docs/usage_c_api.md @@ -48,89 +48,89 @@ Let's break up the example and explain each part: 1. The stack -The `RabbitizerInstruction` type is the type `rabbitizer` uses to represent an -instruction. It is a simple struct which doesn't need dynamic memory -allocation of any kind, so it can be declared as an automatic variable and live -in the stack, without worrying about pointers and such. - -The other stack variables should be self-explanatory. `word` is a 32-bit word -representing a raw MIPS instruction (spoiler, it is an `lw`). `rabbitizer` -needs to know the `vram` address of the instruction it is decoding, so we -initialize with a place-holder one. `buffer` and `bufferSize` will be used for -storing the disassembled string. - -```c -int main() { - RabbitizerInstruction instr; - uint32_t word = 0x8D4A7E18; - uint32_t vram = 0x80000000; - char *buffer; - size_t bufferSize; -``` + The `RabbitizerInstruction` type is the type `rabbitizer` uses to represent an + instruction. It is a simple struct which doesn't need dynamic memory + allocation of any kind, so it can be declared as an automatic variable and live + in the stack, without worrying about pointers and such. + + The other stack variables should be self-explanatory. `word` is a 32-bit word + representing a raw MIPS instruction (spoiler, it is an `lw`). `rabbitizer` + needs to know the `vram` address of the instruction it is decoding, so we + initialize with a place-holder one. `buffer` and `bufferSize` will be used for + storing the disassembled string. + + ```c + int main() { + RabbitizerInstruction instr; + uint32_t word = 0x8D4A7E18; + uint32_t vram = 0x80000000; + char *buffer; + size_t bufferSize; + ``` 2. Initializing -To initialize our `instr` we need to call the pair `RabbitizerInstruction_init` -and `RabbitizerInstruction_processUniqueId`. `RabbitizerInstruction_init` -initialises all the members of the struct so it doesn't contain garbage data -anymore, while `RabbitizerInstruction_processUniqueId` does the heavy lifting of -identifying the actual instruction id out of the `word` we passed. + To initialize our `instr` we need to call the pair `RabbitizerInstruction_init` + and `RabbitizerInstruction_processUniqueId`. `RabbitizerInstruction_init` + initialises all the members of the struct so it doesn't contain garbage data + anymore, while `RabbitizerInstruction_processUniqueId` does the heavy lifting + of identifying the actual instruction id out of the `word` we passed. -A `RabbitizerInstruction` variable is not considered fully initialized until it -has been passed to this pair of functions. + A `RabbitizerInstruction` variable is not considered fully initialized until + it has been passed to this pair of functions. -```c - RabbitizerInstruction_init(&instr, word, vram); - RabbitizerInstruction_processUniqueId(&instr); -``` + ```c + RabbitizerInstruction_init(&instr, word, vram); + RabbitizerInstruction_processUniqueId(&instr); + ``` 3. Disassembling into a string -To disassemble the passed word as a string we can call -`RabbitizerInstruction_disassemble`. This function expects a `char` buffer to -fill, which should have enough space to hold the resulting string. To know how -big this buffer needs to be we should use the -`RabbitizerInstruction_getSizeForBuffer` function which calculates a size big -enough to hold the disassembled string for the passed instruction (without -taking into account the finalizing NUL character, similar to how `strlen` -behaves). + To disassemble the passed word as a string we can call + `RabbitizerInstruction_disassemble`. This function expects a `char` buffer to + fill, which should have enough space to hold the resulting string. To know how + big this buffer needs to be we should use the + `RabbitizerInstruction_getSizeForBuffer` function which calculates a size big + enough to hold the disassembled string for the passed instruction (without + taking into account the finalizing NUL character, similar to how `strlen` + behaves). -With this information we can just `malloc` our buffer and call -`RabbitizerInstruction_disassemble` to get our disassembled instruction. + With this information we can just `malloc` our buffer and call + `RabbitizerInstruction_disassemble` to get our disassembled instruction. -(Ignore the extra `0` and `NULL` arguments for now, they will be discussed later) + (Ignore the extra `0` and `NULL` arguments for now, they will be discussed later) -```c - bufferSize = RabbitizerInstruction_getSizeForBuffer(&instr, 0, 0); - buffer = malloc(bufferSize + 1); + ```c + bufferSize = RabbitizerInstruction_getSizeForBuffer(&instr, 0, 0); + buffer = malloc(bufferSize + 1); - RabbitizerInstruction_disassemble(&instr, buffer, NULL, 0, 0); -``` + RabbitizerInstruction_disassemble(&instr, buffer, NULL, 0, 0); + ``` 4. Printing -Not much to say here, just print the disassembled instruction to `stdout`. + Not much to say here, just print the disassembled instruction to `stdout`. -```c - printf("%s\n", buffer); -``` + ```c + printf("%s\n", buffer); + ``` 5. Clean-up -Finally since we know we won't be using the produced string or the instruction -we just `free` and `RabbitizerInstruction_destroy` them. + Finally since we know we won't be using the produced string or the instruction + we just `free` and `RabbitizerInstruction_destroy` them. -As a curiosity, `RabbitizerInstruction_destroy` currently does nothing, but -exists in case some destruction is needed in the future, so it recommended to -call this function as a future-proof method. + As a curiosity, `RabbitizerInstruction_destroy` currently does nothing, but + exists in case some destruction is needed in the future, so it recommended to + call this function as a future-proof method. -```c - free(buffer); - RabbitizerInstruction_destroy(&instr); + ```c + free(buffer); + RabbitizerInstruction_destroy(&instr); - return 0; -} -``` + return 0; + } + ``` ## Overriding the immediate diff --git a/include/common/RabbitizerVersion.h b/include/common/RabbitizerVersion.h index 627778ed..d2f16173 100644 --- a/include/common/RabbitizerVersion.h +++ b/include/common/RabbitizerVersion.h @@ -14,10 +14,13 @@ extern "C" { // Header version #define RAB_VERSION_MAJOR 1 #define RAB_VERSION_MINOR 7 -#define RAB_VERSION_PATCH 9 +#define RAB_VERSION_PATCH 10 #define RAB_VERSION_STR RAB_STRINGIFY(RAB_VERSION_MAJOR) "." RAB_STRINGIFY(RAB_VERSION_MINOR) "." RAB_STRINGIFY(RAB_VERSION_PATCH) +#define RAB_VERSION_AUTHOR "Decompollaborate" + + // Compiled library version extern const int RabVersion_Major; extern const int RabVersion_Minor; @@ -25,6 +28,8 @@ extern const int RabVersion_Patch; extern const char RabVersion_Str[]; +extern const char RabVersion_Author[]; + #ifdef __cplusplus } diff --git a/pyproject.toml b/pyproject.toml index 342f7217..301190d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [project] name = "rabbitizer" # Version should be synced with include/common/RabbitizerVersion.h -version = "1.7.9" +version = "1.7.10" description = "MIPS instruction decoder" # license = "MIT" readme = "README.md" diff --git a/rabbitizer/__init__.pyi b/rabbitizer/__init__.pyi index 93d0fd27..c0b50d87 100644 --- a/rabbitizer/__init__.pyi +++ b/rabbitizer/__init__.pyi @@ -5,6 +5,10 @@ from __future__ import annotations +__version_info__: tuple[int, int, int] +__version__: str +__author__: str + from .Utils import * from .Enum import * diff --git a/rabbitizer/enums/enums_utils.h b/rabbitizer/enums/enums_utils.h index ffc2c965..f90a01f6 100644 --- a/rabbitizer/enums/enums_utils.h +++ b/rabbitizer/enums/enums_utils.h @@ -1,8 +1,8 @@ /* SPDX-FileCopyrightText: © 2022-2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -#ifndef RABBITIZER_ENUMS_UTILS_H -#define RABBITIZER_ENUMS_UTILS_H +#ifndef PYRABBITIZER_ENUMS_UTILS_H +#define PYRABBITIZER_ENUMS_UTILS_H #pragma once diff --git a/rabbitizer/rabbitizer_macro_utilities.h b/rabbitizer/rabbitizer_macro_utilities.h new file mode 100644 index 00000000..d6754724 --- /dev/null +++ b/rabbitizer/rabbitizer_macro_utilities.h @@ -0,0 +1,73 @@ +/* SPDX-FileCopyrightText: © 2023 Decompollaborate */ +/* SPDX-License-Identifier: MIT */ + +#ifndef PYRABBITIZER_MACRO_UTILITIES_H +#define PYRABBITIZER_MACRO_UTILITIES_H +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + + +#define RAB_STRCMP_LITERAL(var, literal) strncmp(var, literal, sizeof(literal) - 1) + + +#define DECL_RAB_TYPE(typeName, memberName) \ + typedef struct PyRabbitizer##typeName { \ + PyObject_HEAD \ + Rabbitizer##typeName memberName; \ + } PyRabbitizer##typeName; \ + \ + extern PyTypeObject rabbitizer_type_##typeName##_TypeObject; \ + \ + int rabbitizer_type_##typeName##_TypeObject_Check(PyObject *object); \ + int rabbitizer_type_##typeName##_Converter_Optional(PyObject *object, PyRabbitizer##typeName **address); + +#define DEF_RAB_TYPE(typeName) \ + /* Returns positive if the argument is an instance, 0 if it isn't or negative on error */ \ + int rabbitizer_type_##typeName##_TypeObject_Check(PyObject *object) { \ + int isInstance = PyObject_IsInstance(object, (PyObject*)&rabbitizer_type_##typeName##_TypeObject); \ + \ + if (isInstance < 0) { \ + /* An error happened */ \ + /* PyObject_IsInstance already sets an exception, so nothing else to do here */ \ + return -1; \ + } \ + \ + if (isInstance == 0) { \ + /* `object` isn't an instance */ \ + return 0; \ + } \ + \ + return 1; \ + } \ + \ + int rabbitizer_type_##typeName##_Converter_Optional(PyObject *object, PyRabbitizer##typeName **address) { \ + int instanceCheck; \ + \ + if ((object == NULL) || (address == NULL)) { \ + PyErr_Format(PyExc_RuntimeError, "%s: Internal error", __func__); \ + return 0; /* fail */ \ + } \ + \ + if (object == Py_None) { \ + *address = NULL; \ + return 1; /* successful */ \ + } \ + \ + instanceCheck = rabbitizer_type_##typeName##_TypeObject_Check(object); \ + if (instanceCheck < 0) { \ + return 0; /* fail */ \ + } \ + if (instanceCheck > 0) { \ + *address = (PyRabbitizer##typeName *)object; \ + return 1; /* successful */ \ + } \ + \ + PyErr_Format(PyExc_TypeError, "argument must be %s or None, not %s", rabbitizer_type_##typeName##_TypeObject.tp_name, object->ob_type->tp_name); \ + return 0; /* fail */ \ + } + + +#endif diff --git a/rabbitizer/rabbitizer_module.c b/rabbitizer/rabbitizer_module.c index 2fd6be00..96037b9f 100644 --- a/rabbitizer/rabbitizer_module.c +++ b/rabbitizer/rabbitizer_module.c @@ -7,6 +7,7 @@ #include "common/Utils.h" #include "instructions/RabbitizerInstrId.h" +#include "common/RabbitizerVersion.h" typedef enum ModuleAttributeCategory { @@ -123,12 +124,52 @@ static int rabbitizer_module_attributes_Initialize(PyObject *module) { return -1; } +#define rabbitizer_module_method___getattr___docs "Hacky way to get read-only global variables" -static PyModuleDef rabbitizer_module = { +PyObject *rabbitizer_module_method___getattr__(UNUSED void *self, PyObject *args, PyObject *kwds) { + static char *kwlist[] = { "name", NULL }; + const char *attributeName = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &attributeName)) { + return NULL; + } + + if (attributeName == NULL) { + PyErr_Format(PyExc_AssertionError, "%s: assert(attributeName != NULL)", __func__); + return NULL; + } + + if (RAB_STRCMP_LITERAL(attributeName, "__version_info__") == 0) { + return Py_BuildValue("(iii)", RabVersion_Major, RabVersion_Minor, RabVersion_Patch); + } + if (RAB_STRCMP_LITERAL(attributeName, "__version__") == 0) { + return PyUnicode_FromString(RabVersion_Str); + } + if (RAB_STRCMP_LITERAL(attributeName, "__author__") == 0) { + return PyUnicode_FromString(RabVersion_Author); + } + + PyErr_Format(PyExc_AttributeError, "module '%s' has no attribute '%s'", + rabbitizer_module.m_name, attributeName); + return NULL; +} + +#define METHOD_NO_ARGS(name) { #name, (PyCFunction) (void *) rabbitizer_module_method_##name, METH_NOARGS, PyDoc_STR(rabbitizer_module_method_##name##_docs) } +#define METHOD_ARGS(name) { #name, (PyCFunction) (void *) rabbitizer_module_method_##name, METH_VARARGS | METH_KEYWORDS, PyDoc_STR(rabbitizer_module_method_##name##_docs) } + +PyMethodDef rabbitizer_module_methods[] = { + METHOD_ARGS(__getattr__), + + { 0 }, +}; + + +PyModuleDef rabbitizer_module = { PyModuleDef_HEAD_INIT, .m_name = "rabbitizer", .m_doc = "", .m_size = -1, + .m_methods = rabbitizer_module_methods, }; PyMODINIT_FUNC PyInit_rabbitizer(void) { diff --git a/rabbitizer/rabbitizer_module.h b/rabbitizer/rabbitizer_module.h index 2800ff52..c9a261c6 100644 --- a/rabbitizer/rabbitizer_module.h +++ b/rabbitizer/rabbitizer_module.h @@ -1,48 +1,39 @@ /* SPDX-FileCopyrightText: © 2022-2023 Decompollaborate */ /* SPDX-License-Identifier: MIT */ -#ifndef RABBITIZER_MODULE_H -#define RABBITIZER_MODULE_H +#ifndef PYRABBITIZER_MODULE_H +#define PYRABBITIZER_MODULE_H #pragma once #define PY_SSIZE_T_CLEAN #include #include "structmember.h" +#include "rabbitizer_macro_utilities.h" #include "enums/enums_utils.h" #include "instructions/RabbitizerInstruction.h" -#include "analysis/RabbitizerTrackedRegisterState.h" #include "analysis/RabbitizerLoPairingInfo.h" +#include "analysis/RabbitizerTrackedRegisterState.h" +#include "analysis/RabbitizerRegistersTracker.h" -// TODO: clean up this... - - -typedef struct PyRabbitizerInstruction { - PyObject_HEAD - RabbitizerInstruction instr; -} PyRabbitizerInstruction; -typedef struct PyRabbitizerTrackedRegisterState { - PyObject_HEAD - RabbitizerTrackedRegisterState registerState; -} PyRabbitizerTrackedRegisterState; +// TODO: clean up this... -typedef struct PyRabbitizerLoPairingInfo { - PyObject_HEAD - RabbitizerLoPairingInfo pairingInfo; -} PyRabbitizerLoPairingInfo; +extern PyModuleDef rabbitizer_module; PyObject *rabbitizer_submodule_Utils_Init(void); extern PyTypeObject rabbitizer_global_config_TypeObject; extern PyTypeObject rabbitizer_type_Enum_TypeObject; -extern PyTypeObject rabbitizer_type_Instruction_TypeObject; -extern PyTypeObject rabbitizer_type_LoPairingInfo_TypeObject; -extern PyTypeObject rabbitizer_type_TrackedRegisterState_TypeObject; -extern PyTypeObject rabbitizer_type_RegistersTracker_TypeObject; + +DECL_RAB_TYPE(Instruction, instr) +DECL_RAB_TYPE(LoPairingInfo, pairingInfo) +DECL_RAB_TYPE(TrackedRegisterState, registerState) +DECL_RAB_TYPE(RegistersTracker, tracker) + DECL_ENUM(Abi) DECL_ENUM(InstrCategory) diff --git a/rabbitizer/rabbitizer_type_Instruction.c b/rabbitizer/rabbitizer_type_Instruction.c index 967cc16b..a4d9566f 100644 --- a/rabbitizer/rabbitizer_type_Instruction.c +++ b/rabbitizer/rabbitizer_type_Instruction.c @@ -610,7 +610,7 @@ static PyMethodDef rabbitizer_type_Instruction_methods[] = { METHOD_ARGS(disassemble, "description"), - METHOD_ARGS(__reduce__, ""), + METHOD_NO_ARGS(__reduce__, ""), { 0 }, }; @@ -672,6 +672,9 @@ static PyObject *rabbitizer_type_Instruction_str(PyRabbitizerInstruction *self) } +DEF_RAB_TYPE(Instruction) + + PyTypeObject rabbitizer_type_Instruction_TypeObject = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rabbitizer.Instruction", @@ -688,3 +691,4 @@ PyTypeObject rabbitizer_type_Instruction_TypeObject = { .tp_methods = rabbitizer_type_Instruction_methods, .tp_getset = rabbitizer_type_Instruction_getsetters, }; + diff --git a/rabbitizer/rabbitizer_type_LoPairingInfo.c b/rabbitizer/rabbitizer_type_LoPairingInfo.c index e1132d6b..cb594d80 100644 --- a/rabbitizer/rabbitizer_type_LoPairingInfo.c +++ b/rabbitizer/rabbitizer_type_LoPairingInfo.c @@ -32,6 +32,9 @@ static PyMemberDef rabbitizer_type_LoPairingInfo_members[] = { }; +DEF_RAB_TYPE(LoPairingInfo) + + PyTypeObject rabbitizer_type_LoPairingInfo_TypeObject = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rabbitizer.LoPairingInfo", diff --git a/rabbitizer/rabbitizer_type_RegistersTracker.c b/rabbitizer/rabbitizer_type_RegistersTracker.c index 485ef056..f160d806 100644 --- a/rabbitizer/rabbitizer_type_RegistersTracker.c +++ b/rabbitizer/rabbitizer_type_RegistersTracker.c @@ -3,15 +3,6 @@ #include "rabbitizer_module.h" -#include "analysis/RabbitizerRegistersTracker.h" - - -typedef struct PyRabbitizerRegistersTracker { - PyObject_HEAD - RabbitizerRegistersTracker tracker; -} PyRabbitizerRegistersTracker; - - static void rabbitizer_type_RegistersTracker_dealloc(PyRabbitizerRegistersTracker *self) { RabbitizerRegistersTracker_destroy(&self->tracker); Py_TYPE(self)->tp_free((PyObject *) self); @@ -22,7 +13,7 @@ static int rabbitizer_type_RegistersTracker_init(PyRabbitizerRegistersTracker *s PyRabbitizerRegistersTracker *pyOther = NULL; RabbitizerRegistersTracker *other = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!", kwlist, &rabbitizer_type_RegistersTracker_TypeObject, &pyOther)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&", kwlist, rabbitizer_type_RegistersTracker_Converter_Optional, &pyOther)) { return -1; } @@ -119,7 +110,7 @@ static PyObject *rabbitizer_type_RegistersTracker_processLui(PyRabbitizerRegiste PyRabbitizerInstruction *pyPrevInstr = NULL; RabbitizerInstruction *prevInstr = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!i|O!", kwlist, &rabbitizer_type_Instruction_TypeObject, &instr, &instrOffset, &rabbitizer_type_Instruction_TypeObject, &pyPrevInstr)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!i|O&", kwlist, &rabbitizer_type_Instruction_TypeObject, &instr, &instrOffset, rabbitizer_type_Instruction_Converter_Optional, &pyPrevInstr)) { return NULL; } @@ -302,6 +293,9 @@ static PySequenceMethods example_classSeqMethods = { }; +DEF_RAB_TYPE(RegistersTracker) + + PyTypeObject rabbitizer_type_RegistersTracker_TypeObject = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rabbitizer.RegistersTracker", diff --git a/rabbitizer/rabbitizer_type_TrackedRegisterState.c b/rabbitizer/rabbitizer_type_TrackedRegisterState.c index b0e6ae9b..502c222a 100644 --- a/rabbitizer/rabbitizer_type_TrackedRegisterState.c +++ b/rabbitizer/rabbitizer_type_TrackedRegisterState.c @@ -85,6 +85,9 @@ static PyGetSetDef rabbitizer_type_TrackedRegisterState_getsetters[] = { }; +DEF_RAB_TYPE(TrackedRegisterState) + + PyTypeObject rabbitizer_type_TrackedRegisterState_TypeObject = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rabbitizer.TrackedRegisterState", diff --git a/setup.py b/setup.py index a50116cb..f0d3ec91 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ srcPath = Path("src") sourcesList = [str(x) for x in bindingsPath.glob("**/*.c")] + [str(x) for x in srcPath.glob("**/*.c")] +headersList = [str(x) for x in bindingsPath.glob("**/*.h")] + [str(x) for x in srcPath.glob("**/*.h")] extraCompileArgs = ["-std=c11", "-Wall", "-g",] if platform.system() == "Linux": @@ -24,6 +25,7 @@ sources=sourcesList, include_dirs=["include", "rabbitizer", "tables"], extra_compile_args = extraCompileArgs, + depends=headersList, ), ], ) diff --git a/src/common/RabbitizerVersion.c b/src/common/RabbitizerVersion.c index 72265634..56859e87 100644 --- a/src/common/RabbitizerVersion.c +++ b/src/common/RabbitizerVersion.c @@ -8,3 +8,5 @@ const int RabVersion_Minor = RAB_VERSION_MINOR; const int RabVersion_Patch = RAB_VERSION_PATCH; const char RabVersion_Str[] = RAB_VERSION_STR; + +const char RabVersion_Author[] = RAB_VERSION_AUTHOR;