Add support for contextual assembly patching

This commit is contained in:
Romain Thomas
2025-07-13 18:09:50 +02:00
parent 5c7c8222ce
commit c40dcea762
28 changed files with 723 additions and 36 deletions
+9 -3
View File
@@ -367,9 +367,12 @@ void create<Binary>(nb::module_& m) {
)doc"_doc
)
.def("assemble", [] (Binary& self, uint64_t address, const std::string& Asm) {
return nb::to_bytes(self.assemble(address, Asm));
}, "address"_a, "assembly"_a,
.def("assemble",
[] (Binary& self, uint64_t address, const std::string& Asm,
assembly::AssemblerConfig& config)
{
return nb::to_bytes(self.assemble(address, Asm, config));
}, "address"_a, "assembly"_a, "config"_a = assembly::AssemblerConfig::default_config(),
R"doc(
Assemble **and patch** the provided assembly code at the specified address.
@@ -383,6 +386,9 @@ void create<Binary>(nb::module_& m) {
xor rax, rbx;
mov rcx, rax;
""")
If you need to configure the assembly engine or to define addresses for
symbols, you can provide your own :class:`~.assembly.AssemblerConfig` instance.
)doc"_doc
)
+1
View File
@@ -2,6 +2,7 @@ target_sources(pyLIEF PRIVATE
init.cpp
pyEngine.cpp
pyInstruction.cpp
pyAssemblerConfig.cpp
)
add_subdirectory(aarch64)
+2
View File
@@ -12,6 +12,7 @@
namespace LIEF::assembly {
class Engine;
class Instruction;
class AssemblerConfig;
}
namespace LIEF::assembly::py {
@@ -20,6 +21,7 @@ void init(nb::module_& m) {
create<LIEF::assembly::Engine>(mod);
create<LIEF::assembly::Instruction>(mod);
create<LIEF::assembly::AssemblerConfig>(mod);
aarch64::py::init(mod);
x86::py::init(mod);
+80
View File
@@ -0,0 +1,80 @@
#include "LIEF/asm/AssemblerConfig.hpp"
#include "asm/pyAssembly.hpp"
#include <nanobind/stl/string.h>
#include <nanobind/trampoline.h>
#include "nanobind/extra/stl/lief_optional.h"
#include "nanobind/extra/stl/lief_optional.h"
namespace LIEF::assembly::py {
class PyAssemblerConfig : public assembly::AssemblerConfig {
public:
static constexpr auto NB_NUM_SLOTS = 3;
NB_TRAMPOLINE(assembly::AssemblerConfig, NB_NUM_SLOTS);
optional<uint64_t> resolve_symbol(const std::string& name) override {
NB_OVERRIDE(resolve_symbol, name);
}
~PyAssemblerConfig() override = default;
};
template<>
void create<assembly::AssemblerConfig>(nb::module_& m) {
nb::class_<assembly::AssemblerConfig, PyAssemblerConfig> obj(m, "AssemblerConfig",
R"doc(
This class exposes the different elements that can be configured to assemble
code.
)doc"_doc
);
nb::enum_<assembly::AssemblerConfig::DIALECT>(obj, "DIALECT",
"The different supported dialects"_doc
)
.value("DEFAULT_DIALECT", assembly::AssemblerConfig::DIALECT::DEFAULT_DIALECT)
.value("X86_INTEL", assembly::AssemblerConfig::DIALECT::X86_INTEL,
"Intel syntax"_doc)
.value("X86_ATT", assembly::AssemblerConfig::DIALECT::X86_ATT,
"AT&T syntax"_doc)
;
obj
.def(nb::init<>())
.def_static("default_config", &assembly::AssemblerConfig::default_config,
"Default configuration"_doc
)
.def_rw("dialect", &assembly::AssemblerConfig::dialect,
"The dialect of the input assembly code"_doc
)
.def("resolve_symbol", &assembly::AssemblerConfig::resolve_symbol,
R"doc(
This function aims to be overloaded in order to resolve symbols used
in the assembly listing.
For instance, given this assembly code:
.. code-block:: text
0x1000: mov rdi, rbx
0x1003: call _my_function
The function ``_my_function`` will remain undefined unless we return its
address in :meth:`~.resolve_symbol`:
.. code-block:: python
class MyConfig(lief.assembly.AssemblerConfig):
def __init__(self):
super().__init__() # This is important
@override
def resolve_symbol(self, name: str) -> int | None:
if name == '_my_function':
return 0x4000
return None # Or super().resolve_symbol(name)
)doc"_doc, "name"_a)
;
}
}
+2 -1
View File
@@ -311,13 +311,14 @@ void init(nb::module_& m) {
LIEF::py::init_hash(m);
LIEF::py::init_json(m);
LIEF::assembly::py::init(m);
LIEF::py::init_abstract(m);
LIEF::dwarf::py::init(m);
LIEF::pdb::py::init(m);
LIEF::objc::py::init(m);
LIEF::dsc::py::init(m);
LIEF::assembly::py::init(m);
#if defined(LIEF_ELF_SUPPORT)
LIEF::ELF::py::init(m);