diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b59f9b..2aa6335 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.11.1] - 2024-07-12 + +### Added + +- `Instruction.isFunctionCall`: Checks if the given instruciton is a function + call. + - If `config.toolchainTweaks_treatJAsUnconditionalBranch` is turned off then + `j` instructions will be considered as function calls too. Useful for tail + call optimizations. + +### Fixed + +- Fix registers not being cleared after a `j` tail call, which was messing with + the pointer pairing logic. + ## [1.11.0] - 2024-05-22 ### Added @@ -618,6 +633,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - First version [unreleased]: https://github.com/Decompollaborate/rabbitizer/compare/master...develop +[1.11.1]: https://github.com/Decompollaborate/rabbitizer/compare/1.11.0...1.11.1 [1.11.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.10.0...1.11.0 [1.10.0]: https://github.com/Decompollaborate/rabbitizer/compare/1.9.5...1.10.0 [1.9.5]: https://github.com/Decompollaborate/rabbitizer/compare/1.9.4...1.9.5 diff --git a/Cargo.toml b/Cargo.toml index dfd18e3..9982b37 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.11.0" +version = "1.11.1" edition = "2021" authors = ["Anghelo Carvajal "] description = "MIPS instruction decoder" diff --git a/README.md b/README.md index 9cf3dc0..71fcb42 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ If you use a `requirements.txt` file in your repository, then you can add this library with the following line: ```txt -rabbitizer>=1.11.0,<2.0.0 +rabbitizer>=1.11.1,<2.0.0 ``` ### Development version @@ -109,7 +109,7 @@ cargo add rabbitizer Or you can add it manually to your `Cargo.toml`: ```toml -rabbitizer = "1.11.0" +rabbitizer = "1.11.1" ``` See this crate at . diff --git a/cplusplus/include/instructions/InstructionBase.hpp b/cplusplus/include/instructions/InstructionBase.hpp index 520960f..639cd49 100644 --- a/cplusplus/include/instructions/InstructionBase.hpp +++ b/cplusplus/include/instructions/InstructionBase.hpp @@ -198,6 +198,7 @@ namespace rabbitizer { bool isLikelyHandwritten() const; bool isNop() const; bool isUnconditionalBranch() const; + bool isFunctionCall() const; bool isReturn() const; bool isJumptableJump() const; diff --git a/cplusplus/src/instructions/InstructionBase.cpp b/cplusplus/src/instructions/InstructionBase.cpp index c021218..e011ae0 100644 --- a/cplusplus/src/instructions/InstructionBase.cpp +++ b/cplusplus/src/instructions/InstructionBase.cpp @@ -787,6 +787,9 @@ bool InstructionBase::isNop() const { bool InstructionBase::isUnconditionalBranch() const { return RabbitizerInstruction_isUnconditionalBranch(&this->instr); } +bool InstructionBase::isFunctionCall() const { + return RabbitizerInstruction_isFunctionCall(&this->instr); +} bool InstructionBase::isReturn() const { return RabbitizerInstruction_isReturn(&this->instr); diff --git a/include/common/RabbitizerVersion.h b/include/common/RabbitizerVersion.h index 6658799..9a7c785 100644 --- a/include/common/RabbitizerVersion.h +++ b/include/common/RabbitizerVersion.h @@ -14,7 +14,7 @@ extern "C" { // Header version #define RAB_VERSION_MAJOR 1 #define RAB_VERSION_MINOR 11 -#define RAB_VERSION_PATCH 0 +#define RAB_VERSION_PATCH 1 #define RAB_VERSION_STR RAB_STRINGIFY(RAB_VERSION_MAJOR) "." RAB_STRINGIFY(RAB_VERSION_MINOR) "." RAB_STRINGIFY(RAB_VERSION_PATCH) diff --git a/include/instructions/RabbitizerInstruction.h b/include/instructions/RabbitizerInstruction.h index 6acf754..2c7e878 100644 --- a/include/instructions/RabbitizerInstruction.h +++ b/include/instructions/RabbitizerInstruction.h @@ -241,6 +241,8 @@ NODISCARD NON_NULL(1) PURE bool RabbitizerInstruction_isNop(const RabbitizerInstruction *self); NODISCARD NON_NULL(1) PURE bool RabbitizerInstruction_isUnconditionalBranch(const RabbitizerInstruction *self); +NODISCARD NON_NULL(1) PURE +bool RabbitizerInstruction_isFunctionCall(const RabbitizerInstruction *self); NODISCARD NON_NULL(1) PURE bool RabbitizerInstruction_isReturn(const RabbitizerInstruction *self); diff --git a/pyproject.toml b/pyproject.toml index 960aea6..daa2019 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.11.0" +version = "1.11.1" description = "MIPS instruction decoder" # license = "MIT" readme = "README.md" diff --git a/rabbitizer/rabbitizer.pyi b/rabbitizer/rabbitizer.pyi index 3039d19..d868e84 100644 --- a/rabbitizer/rabbitizer.pyi +++ b/rabbitizer/rabbitizer.pyi @@ -181,7 +181,7 @@ class Instruction: def isNop(self) -> bool: """Check if the instruction is literally the `nop` instruction.""" def isUnconditionalBranch(self) -> bool: - """Check if the instruction is an instruction that will always (unconditionally). + """Check if the instruction is an instruction that will always branch unconditionally. This is always true for the `b` instruction. @@ -190,6 +190,17 @@ class Instruction: can be configured with the `config.toolchainTweaks_treatJAsUnconditionalBranch` option. """ + def isFunctionCall(self) -> bool: + """Check if this is an instruction used for function calls. + + This is always true for "and link" instructions. + + Some compilers use the `j` instruction for tail call optimizations, meaning + we may require to give special treatment to this instruction if we are + analyzing code emitted by one of those compilers, like clearing registers + after a tail call. This can be configured by turning off the + `config.toolchainTweaks_treatJAsUnconditionalBranch` option. + """ def isReturn(self) -> bool: """Check if the instruction and its register is the one usually used for diff --git a/rabbitizer/rabbitizer_type_Instruction.c b/rabbitizer/rabbitizer_type_Instruction.c index a06b304..20e2a7c 100644 --- a/rabbitizer/rabbitizer_type_Instruction.c +++ b/rabbitizer/rabbitizer_type_Instruction.c @@ -386,6 +386,7 @@ DEF_METHOD_BOOL(isImplemented) DEF_METHOD_BOOL(isLikelyHandwritten) DEF_METHOD_BOOL(isNop) DEF_METHOD_BOOL(isUnconditionalBranch) +DEF_METHOD_BOOL(isFunctionCall) DEF_METHOD_BOOL(isReturn) DEF_METHOD_BOOL(isJumptableJump) @@ -650,6 +651,7 @@ static PyMethodDef rabbitizer_type_Instruction_methods[] = { METHOD_NO_ARGS(isLikelyHandwritten, ""), METHOD_NO_ARGS(isNop, ""), METHOD_NO_ARGS(isUnconditionalBranch, ""), + METHOD_NO_ARGS(isFunctionCall, ""), METHOD_NO_ARGS(isReturn, ""), METHOD_NO_ARGS(isJumptableJump, ""), diff --git a/rust/src/instruction.rs b/rust/src/instruction.rs index f300c2a..bc5afde 100644 --- a/rust/src/instruction.rs +++ b/rust/src/instruction.rs @@ -84,6 +84,7 @@ extern "C" { fn RabbitizerInstruction_isLikelyHandwritten(self_: *const Instruction) -> bool; fn RabbitizerInstruction_isNop(self_: *const Instruction) -> bool; fn RabbitizerInstruction_isUnconditionalBranch(self_: *const Instruction) -> bool; + fn RabbitizerInstruction_isFunctionCall(self_: *const Instruction) -> bool; fn RabbitizerInstruction_isReturn(self_: *const Instruction) -> bool; fn RabbitizerInstruction_isJumptableJump(self_: *const Instruction) -> bool; @@ -546,6 +547,9 @@ impl Instruction { pub fn is_unconditional_branch(&self) -> bool { unsafe { RabbitizerInstruction_isUnconditionalBranch(self) } } + pub fn is_function_call(&self) -> bool { + unsafe { RabbitizerInstruction_isFunctionCall(self) } + } pub fn is_return(&self) -> bool { unsafe { RabbitizerInstruction_isReturn(self) } diff --git a/src/analysis/RabbitizerRegistersTracker.c b/src/analysis/RabbitizerRegistersTracker.c index 4cdd329..ea63bcf 100644 --- a/src/analysis/RabbitizerRegistersTracker.c +++ b/src/analysis/RabbitizerRegistersTracker.c @@ -147,7 +147,7 @@ void RabbitizerRegistersTracker_overwriteRegisters(RabbitizerRegistersTracker *s void RabbitizerRegistersTracker_unsetRegistersAfterFuncCall(RabbitizerRegistersTracker *self, UNUSED const RabbitizerInstruction *instr, const RabbitizerInstruction *prevInstr) { - if (!RabbitizerInstrDescriptor_doesLink(prevInstr->descriptor)) { + if (!RabbitizerInstruction_isFunctionCall(prevInstr)) { return; } diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c index 32fca44..ab161ae 100644 --- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c +++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c @@ -52,7 +52,7 @@ bool RabbitizerInstruction_isNop(const RabbitizerInstruction *self) { } /** - * Check if the instruction is an instruction that will always (unconditionally). + * Check if the instruction is an instruction that will always branch unconditionally. * * This is always true for the `b` instruction. * @@ -81,6 +81,32 @@ bool RabbitizerInstruction_isUnconditionalBranch(const RabbitizerInstruction *se } } +/** + * Check if this is an instruction used for function calls. + * + * This is always true for "and link" instructions. + * + * Some compilers use the `j` instruction for tail call optimizations, meaning + * we may require to give special treatment to this instruction if we are + * analyzing code emitted by one of those compilers, like clearing registers + * after a tail call. This can be configured by turning off the + * `config.toolchainTweaks_treatJAsUnconditionalBranch` option. + */ +bool RabbitizerInstruction_isFunctionCall(const RabbitizerInstruction *self) { + if (RabbitizerInstrDescriptor_doesLink(self->descriptor)) { + return true; + } + + switch (self->uniqueId) { + case RABBITIZER_INSTR_ID_cpu_j: + case RABBITIZER_INSTR_ID_rsp_j: + return !RabbitizerConfig_Cfg.toolchainTweaks.treatJAsUnconditionalBranch; + + default: + return false; + } +} + /** * Check if the instruction and its register is the one usually used for * returning from a function.