From 75e71877364724795f2585da99555f407883742c Mon Sep 17 00:00:00 2001 From: Diego Argueta Date: Mon, 17 Apr 2023 11:59:35 -0700 Subject: [PATCH] Unicorn 2.x support (#32) --- .editorconfig | 2 +- .github/workflows/ci.yml | 5 +- .gitignore | 1 + CHANGELOG.rst | 74 ++++++++- Makefile | 8 +- README.rst | 2 +- docs/api.rst | 86 +++++++++++ include/unicornlua/compat.hpp | 2 +- include/unicornlua/control_functions.hpp | 41 +++++ include/unicornlua/transaction.hpp | 17 ++ include/unicornlua/unicornlua.hpp | 4 - include/unicornlua/utils.hpp | 2 +- src/basic_control_functions.template | 65 ++++++++ src/control_functions.cpp | 92 +++++++++++ src/engine.cpp | 25 ++- src/hooks.cpp | 145 +++++++++++++++++- src/memory.cpp | 12 +- src/registers_misc.cpp | 14 +- src/template_data/basic_control_functions.lua | 30 ++++ src/{ => template_data}/register_types.lua | 0 src/transaction.cpp | 19 +++ src/unicorn.cpp | 16 +- src/utils.cpp | 13 +- tests/lua/imports.lua | 14 ++ tools/ci/Makefile | 26 +++- tools/render_template.lua | 12 +- 26 files changed, 671 insertions(+), 56 deletions(-) create mode 100644 include/unicornlua/control_functions.hpp create mode 100644 include/unicornlua/transaction.hpp create mode 100644 src/basic_control_functions.template create mode 100644 src/control_functions.cpp create mode 100644 src/template_data/basic_control_functions.lua rename src/{ => template_data}/register_types.lua (100%) create mode 100644 src/transaction.cpp diff --git a/.editorconfig b/.editorconfig index 72498d01..b25d91a5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true charset = utf-8 indent_style = space -[*.{cpp,h,lua,py,template,rst}] +[*.{cpp,hpp,lua,py,template,rst}] end_of_line = lf indent_size = 4 insert_final_newline = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528172b9..35ab92c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,9 @@ jobs: - "5.4" - "luajit-2.0.5" - "luajit-2.1.0-beta3" + unicorn-version: + - "1.0.3" + - "2.0.1" steps: - uses: actions/checkout@v3 - name: Install Lua @@ -31,7 +34,7 @@ jobs: # https://github.com/leafo/gh-actions-luarocks/pull/14 has been merged. uses: hishamhm/gh-actions-luarocks@5013277f6f115c27478f18c1f647f8de98390628 - name: Install Unicorn - run: make -C tools/ci install_unicorn UNICORN_VERSION=1.0.2 + run: make -C tools/ci install_unicorn UNICORN_VERSION=${{ matrix.unicorn-version }} - name: Environment run: luarocks config - name: Install Binding diff --git a/.gitignore b/.gitignore index d0761c62..6423dac8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ tests/c/doctest.h # Autogenerated files: src/constants/ +src/basic_control_functions.cpp src/registers.cpp src/registers_const.cpp include/unicornlua/register_types.hpp diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 825d6930..215446f0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,21 +1,85 @@ Changes ======= -2.2.0 (Unreleased) +2.2.0 (2023-04-17) ------------------ New Features ~~~~~~~~~~~~ -Official support for LuaJIT 2.1. +* Added support for LuaJIT 2.1. +* Added support for Unicorn 2. + +Instead of throwing an error ``unicorn.arch_supported()`` now returns false if +the given architecture is nil. This allows code to easily determine if an +architecture is supported without needing to check the Unicorn version AND assume +that the Unicorn library was compiled with all available architectures. For +example: + +Old way: + +.. code-block:: lua + + local have_ppc + if uc:version()[1] < 2 then + have_ppc = false + else + have_ppc = uc.arch_supported(uc_const.UC_ARCH_PPC) + end + +New way: + +.. code-block:: lua + + local have_ppc = uc.arch_supported(uc_const.UC_ARCH_PPC) + +See `Unicorn's changelog `_ +for the details of API changes, but a summary here: + +Control Functions +***************** + +All ``uc_ctl_*`` macros are their own methods on an engine, minus the ``uc_`` +prefix. For libraries linked to Unicorn 1.x these functions are present, but +will throw an exception if used. + +**The bare ``uc_ctl()`` function is not exposed.** + +Instruction Hooks +***************** + +* x86: CPUID (SYSENTER and SYSCALL were broken before and have been fixed) +* AArch64: MRS, MSR, SYS, SYSL + +Other Hooks +*********** + +See the Unicorn documentation for what these do. + +* ``UC_HOOK_EDGE_GENERATED`` +* ``UC_HOOK_TCG_OPCODE`` + +Bugfixes +~~~~~~~~ + +Added missing hook for x86 SYSENTER and SYSCALL instructions. Before, it used +to call the default instruction hook function, which resulted in a segfault +because the wrong number of arguments were getting passed. Since this never +worked from the beginning, I don't consider this a breaking change. + +``unicorn.arch_supported()`` now checks the first argument given instead of the +last argument. It's only supposed to take one argument, so if used correctly +this changes nothing. If additional arguments are passed (such as mode flags), +this will now ignore them. Other Changes ~~~~~~~~~~~~~ * Add clang-format, use WebKit's style (more or less). -* Autogenerate a bunch of register-related files from templates. **Note:** Some - register type enums values have changed. If you use the symbolic constants - provided in ``registers_const`` this won't affect you. +* Autogenerate a bunch of files from templates to reduce duplicated code. + +**Note:** Some register type enum values have changed. If you use the symbolic +constants provided in ``unicorn.registers_const`` this won't affect you. 2.1.0 (2023-04-08) ------------------ diff --git a/Makefile b/Makefile index cfbeb4b3..1b4d0349 100644 --- a/Makefile +++ b/Makefile @@ -68,8 +68,10 @@ TEST_LUA_SOURCES = $(wildcard tests/lua/*.lua) TEST_HEADERS = $(wildcard tests/c/*.hpp) TEST_CPP_OBJECT_FILES = $(TEST_CPP_SOURCES:.cpp=.$(OBJ_EXTENSION)) +TEMPLATE_DATA_FILES = $(wildcard src/template_data/*.lua) + LIBRARY_DIRECTORIES := $(strip $(LUA_LIBDIR) $(FALLBACK_LUA_LIBDIR) $(UNICORN_LIBDIR) $(PTHREAD_LIBDIR) /usr/lib64 /usr/local/lib) -HEADER_DIRECTORIES := $(strip $(CURDIR)/include $(LUA_INCDIR) $(FALLBACK_LUA_INCDIR) $(UNICORN_INCDIR)) +HEADER_DIRECTORIES := $(strip $(CURDIR)/include $(LUA_INCDIR) $(FALLBACK_LUA_INCDIR) $(UNICORN_INCDIR) /usr/local/include) USER_CXX_FLAGS ?= OTHER_CXXFLAGS := -std=c++11 -DIS_LUAJIT=$(IS_LUAJIT) @@ -186,12 +188,12 @@ src/%.$(OBJ_EXTENSION): src/%.cpp $(AUTOGENERATED_HPP_FILES) $(CXX_CMD) $(CXXFLAGS) -c -o $@ $< -%.cpp: %.template src/register_types.lua +%.cpp: %.template $(TEMPLATE_DATA_FILES) @echo "Generating $@" @$(SET_SEARCH_PATHS); $(LUA) tools/render_template.lua -o $@ $^ -%.hpp: %.template src/register_types.lua +%.hpp: %.template $(TEMPLATE_DATA_FILES) @echo "Generating $@" @$(SET_SEARCH_PATHS); $(LUA) tools/render_template.lua -o $@ $^ diff --git a/README.rst b/README.rst index c91bbc87..29eb5dd3 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ unicorn-lua :alt: Build status :target: https://travis-ci.com/dargueta/unicorn-lua -.. |lua-versions| image:: https://img.shields.io/badge/lua-5.1%20%7C%205.2%20%7C%205.3%20%7C%205.4%20%7C%20LuaJIT2.020%7C%20LuaJIT2.1-blue-blue +.. |lua-versions| image:: https://img.shields.io/badge/lua-5.1%20%7C%205.2%20%7C%205.3%20%7C%205.4%20%7C%20LuaJIT2.0%20%7C%20LuaJIT2.1-blue :alt: Lua versions :target: https://www.lua.org diff --git a/docs/api.rst b/docs/api.rst index 18f759ac..834442e4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -76,6 +76,67 @@ constants for these error codes are in the ``unicorn`` namespace and begin with ``UC_ERR_``. +``ctl_exits_disable()`` +~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_exits_enable()`` +~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_flush_tlb()`` +~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_arch()`` +~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_cpu_model()`` +~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_exits()`` +~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_exits_cnt()`` +~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_mode()`` +~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_page_size()`` +~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_get_timeout()`` +~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_remove_cache(start_addr, end_addr)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_request_cache(address)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_set_cpu_model(model)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_set_exits(exits)`` +~~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + +``ctl_set_page_size(page_size)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*New in 2.2.0 (requires Unicorn 2)* + + ``hook_add(kind, callback, start_address=nil, end_address=nil, udata=nil, ...)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -446,6 +507,31 @@ Arguments ``architecture``: An enum value for the architecture to ask about. Constants are in the ``unicorn`` namespace and begin with ``UC_ARCH_``. +*Changed in 2.2.0:* + +``unicorn.arch_supported`` now returns false if the architecture is nil instead +of crashing. This allows code to easily determine if an architecture is supported +without needing to check the Unicorn version AND assume that the Unicorn library +was compiled with all available architectures. For example: + +Old way: + +.. code-block:: lua + + local have_ppc + if uc:version()[1] < 2 then + have_ppc = false + else + have_ppc = uc.arch_supported(uc_const.UC_ARCH_PPC) + end + +New way: + +.. code-block:: lua + + local have_ppc = uc.arch_supported(uc_const.UC_ARCH_PPC) + + Returns ^^^^^^^ diff --git a/include/unicornlua/compat.hpp b/include/unicornlua/compat.hpp index 012d2827..42076c19 100644 --- a/include/unicornlua/compat.hpp +++ b/include/unicornlua/compat.hpp @@ -49,7 +49,7 @@ LUALIB_API void lua_rawsetp(lua_State* L, int index, const void* p); #ifndef luaL_newlib #define luaL_newlib(L, l) \ (luaL_newlibtable((L), (l)), luaL_setfuncs((L), (l), 0)) -#endif // luaL_newlib +#endif // luaL_newlib #endif // LUA_VERSION_NUM < 502 // http://lua-users.org/lists/lua-l/2011-11/msg01149.html diff --git a/include/unicornlua/control_functions.hpp b/include/unicornlua/control_functions.hpp new file mode 100644 index 00000000..a14e3f4c --- /dev/null +++ b/include/unicornlua/control_functions.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "lua.hpp" + +[[noreturn]] int ul_crash_unsupported_operation(lua_State* L); + +#if UC_API_MAJOR >= 2 +int ul_ctl_exits_disable(lua_State* L); +int ul_ctl_exits_enable(lua_State* L); +int ul_ctl_flush_tlb(lua_State* L); +int ul_ctl_get_arch(lua_State* L); +int ul_ctl_get_cpu_model(lua_State* L); +int ul_ctl_get_exits(lua_State* L); +int ul_ctl_get_exits_cnt(lua_State* L); +int ul_ctl_get_mode(lua_State* L); +int ul_ctl_get_page_size(lua_State* L); +int ul_ctl_get_timeout(lua_State* L); +int ul_ctl_remove_cache(lua_State* L); +int ul_ctl_request_cache(lua_State* L); +int ul_ctl_set_cpu_model(lua_State* L); +int ul_ctl_set_exits(lua_State* L); +int ul_ctl_set_page_size(lua_State* L); +#else +#define ul_ctl_exits_disable ul_crash_unsupported_operation +#define ul_ctl_exits_enable ul_crash_unsupported_operation +#define ul_ctl_flush_tlb ul_crash_unsupported_operation +#define ul_ctl_get_arch ul_crash_unsupported_operation +#define ul_ctl_get_cpu_model ul_crash_unsupported_operation +#define ul_ctl_get_exits ul_crash_unsupported_operation +#define ul_ctl_get_exits_cnt ul_crash_unsupported_operation +#define ul_ctl_get_mode ul_crash_unsupported_operation +#define ul_ctl_get_page_size ul_crash_unsupported_operation +#define ul_ctl_get_timeout ul_crash_unsupported_operation +#define ul_ctl_remove_cache ul_crash_unsupported_operation +#define ul_ctl_request_cache ul_crash_unsupported_operation +#define ul_ctl_set_cpu_model ul_crash_unsupported_operation +#define ul_ctl_set_exits ul_crash_unsupported_operation +#define ul_ctl_set_page_size ul_crash_unsupported_operation +#endif diff --git a/include/unicornlua/transaction.hpp b/include/unicornlua/transaction.hpp new file mode 100644 index 00000000..f06a6c75 --- /dev/null +++ b/include/unicornlua/transaction.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#if UC_API_MAJOR >= 2 + +#include "unicornlua/lua.hpp" + +/** + * Create a Lua table representation of a transaction block and push it to the + * Lua stack. + * + * @param L + * @param block + */ +void create_table_from_transaction_block(lua_State* L, const uc_tb* block); + +#endif // UC_API_MAJOR diff --git a/include/unicornlua/unicornlua.hpp b/include/unicornlua/unicornlua.hpp index 95f08444..263443f9 100644 --- a/include/unicornlua/unicornlua.hpp +++ b/include/unicornlua/unicornlua.hpp @@ -11,10 +11,6 @@ #include "unicornlua/compat.hpp" #include "unicornlua/lua.hpp" -#if UC_VERSION_MAJOR != 1 -#error "Library must be compiled against version 1.x of Unicorn." -#endif - /** * The major version number of this Lua library (first part, 1.x.x). */ diff --git a/include/unicornlua/utils.hpp b/include/unicornlua/utils.hpp index 4a82092a..fe0f8237 100644 --- a/include/unicornlua/utils.hpp +++ b/include/unicornlua/utils.hpp @@ -22,7 +22,7 @@ * @note Like lua_error, this function never returns, and should be treated in * exactly the same way. */ -int ul_crash_on_error(lua_State* L, uc_err error); +[[noreturn]] void ul_crash_on_error(lua_State* L, uc_err error); /** * Create a new weak table with the given key mode, and push it onto the stack. diff --git a/src/basic_control_functions.template b/src/basic_control_functions.template new file mode 100644 index 00000000..7a2f9957 --- /dev/null +++ b/src/basic_control_functions.template @@ -0,0 +1,65 @@ +#include + +#include + +#if UC_API_MAJOR >= 2 + +#include "unicornlua/control_functions.hpp" +#include "unicornlua/engine.hpp" +#include "unicornlua/errors.hpp" +#include "unicornlua/lua.hpp" +#include "unicornlua/unicornlua.hpp" +#include "unicornlua/utils.hpp" + + +@ for _, name in ipairs(no_arguments_no_return) do +int ul_ctl_$(name)(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + + uc_err error = uc_ctl_$(name)(engine->get_handle()); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + return 0; +} +@ end + +@ for name, info in pairs(no_arguments_scalar_return) do +int ul_ctl_$(name)(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + uc_engine* handle = engine->get_handle(); + $(info.c_type) result; + + uc_err error = uc_ctl_$(name)(handle, &result); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + + lua_push$(info.lua_type)(L, static_cast(result)); + return 1; +} +@ end + +@ for name, arguments in pairs(scalar_arguments_no_return) do +int ul_ctl_$(name)(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + uc_engine* handle = engine->get_handle(); + +@ for i, arg_info in ipairs(arguments) do + auto arg_$(i) = static_cast<$(arg_info.c_type)>(lua_to$(arg_info.lua_type)(L, $(i + 1))); +@ end + + uc_err error = uc_ctl_$(name)( + handle +@ for i = 1, #arguments, 1 do + , arg_$(i) +@ end + ); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + return 0; +} +@ end + +#endif // UC_API_MAJOR diff --git a/src/control_functions.cpp b/src/control_functions.cpp new file mode 100644 index 00000000..6d1a4211 --- /dev/null +++ b/src/control_functions.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include "unicornlua/errors.hpp" +#include "unicornlua/lua.hpp" + +[[noreturn]] int ul_crash_unsupported_operation(lua_State* L) +{ + (void)L; + throw std::runtime_error( + "Functionality not supported in this version of Unicorn."); +} + +#if UC_API_MAJOR >= 2 +#include +#include + +#include "unicornlua/control_functions.hpp" +#include "unicornlua/engine.hpp" +#include "unicornlua/errors.hpp" +#include "unicornlua/transaction.hpp" +#include "unicornlua/unicornlua.hpp" +#include "unicornlua/utils.hpp" + +int ul_ctl_get_exits(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + uc_engine* handle = engine->get_handle(); + size_t count; + + // Determine how many exit points we have registered. + uc_err error = uc_ctl_get_exits_cnt(handle, &count); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + + // Get the exit points. + std::unique_ptr array(new uint64_t[count]); + error = uc_ctl_get_exits(handle, array.get(), count); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + + // Put the exit points into a Lua table. + lua_createtable(L, static_cast(count), 0); + for (size_t i = 0; i < count; i++) { + lua_pushinteger(L, static_cast(array.get()[i])); + lua_seti(L, -1, static_cast(i)); + } + + return 1; +} + +int ul_ctl_request_cache(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + uc_engine* handle = engine->get_handle(); + + auto address = static_cast(lua_tointeger(L, 2)); + uc_tb tblock; + + uc_err error = uc_ctl_request_cache(handle, address, &tblock); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + + create_table_from_transaction_block(L, &tblock); + return 1; +} + +int ul_ctl_set_exits(lua_State* L) +{ + UCLuaEngine* engine = ul_toluaengine(L, 1); + uc_engine* handle = engine->get_handle(); + + auto n_entries = static_cast(luaL_len(L, 2)); + if (n_entries < 1) + return 0; + + std::unique_ptr entries(new uint64_t[n_entries]); + + // The table argument lists all the exit points. Iterate over these, putting + // them into the array we're about to pass Unicorn. + for (int i = 0; i < static_cast(n_entries); i++) { + lua_geti(L, 2, i + 1); + entries.get()[i] = static_cast(lua_tointeger(L, -1)); + lua_pop(L, 1); + } + + uc_err error = uc_ctl_set_exits(handle, entries.get(), n_entries); + if (error != UC_ERR_OK) + ul_crash_on_error(L, error); + return 0; +} +#endif // UC_API_MAJOR >= 2 diff --git a/src/engine.cpp b/src/engine.cpp index eae8ac9f..b8a2cb50 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,6 +1,7 @@ #include #include "unicornlua/context.hpp" +#include "unicornlua/control_functions.hpp" #include "unicornlua/engine.hpp" #include "unicornlua/errors.hpp" #include "unicornlua/hooks.hpp" @@ -30,10 +31,26 @@ const luaL_Reg kEngineMetamethods[] = { { "__gc", maybe_close }, const luaL_Reg kEngineInstanceMethods[] = { { "close", ul_close }, { "context_restore", ul_context_restore }, - { "context_save", ul_context_save }, { "emu_start", ul_emu_start }, - { "emu_stop", ul_emu_stop }, { "errno", ul_errno }, - { "hook_add", ul_hook_add }, { "hook_del", ul_hook_del }, - { "mem_map", ul_mem_map }, { "mem_protect", ul_mem_protect }, + { "context_save", ul_context_save }, + { "ctl_exits_disable", ul_ctl_exits_disable }, + { "ctl_exits_enable", ul_ctl_exits_enable }, + { "ctl_flush_tlb", ul_ctl_flush_tlb }, { "ctl_get_arch", ul_ctl_get_arch }, + { "ctl_get_cpu_model", ul_ctl_get_cpu_model }, + { "ctl_get_exits", ul_ctl_get_exits }, + { "ctl_get_exits_cnt", ul_ctl_get_exits_cnt }, + { "ctl_get_mode", ul_ctl_get_mode }, + { "ctl_get_page_size", ul_ctl_get_page_size }, + { "ctl_get_timeout", ul_ctl_get_timeout }, + { "ctl_remove_cache", ul_ctl_remove_cache }, + { "ctl_request_cache", ul_ctl_request_cache }, + { "ctl_set_cpu_model", ul_ctl_set_cpu_model }, + { "ctl_set_exits", ul_ctl_set_exits }, + { "ctl_set_page_size", ul_ctl_set_page_size }, + { "emu_start", ul_emu_start }, { "emu_stop", ul_emu_stop }, + { "errno", ul_errno }, { "hook_add", ul_hook_add }, + { "hook_del", ul_hook_del }, { "mem_map", ul_mem_map }, + { "mem_protect", ul_mem_protect }, + // n.b. mem_map_ptr() is irrelevant for Lua { "mem_read", ul_mem_read }, { "mem_regions", ul_mem_regions }, { "mem_unmap", ul_mem_unmap }, { "mem_write", ul_mem_write }, { "query", ul_query }, { "reg_read", ul_reg_read }, diff --git a/src/hooks.cpp b/src/hooks.cpp index cc3e8a2f..f4b07a98 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -1,10 +1,12 @@ +#include + #include -#include #include "unicornlua/engine.hpp" #include "unicornlua/errors.hpp" #include "unicornlua/hooks.hpp" #include "unicornlua/lua.hpp" +#include "unicornlua/transaction.hpp" #include "unicornlua/utils.hpp" Hook::Hook(lua_State* L, uc_engine* engine) @@ -226,6 +228,112 @@ static bool invalid_mem_access_hook(uc_engine* uc, uc_mem_type type, return return_value != 0; } +static void generic_hook_with_no_arguments(uc_engine* uc, void* user_data) +{ + auto hook = reinterpret_cast(user_data); + lua_State* L = hook->L(); + + ul_find_lua_engine(L, uc); + hook->push_user_data(); + lua_call(L, 2, 0); +} + +#if UC_API_MAJOR >= 2 +static bool cpuid_hook(uc_engine* uc, void* user_data) +{ + auto hook = reinterpret_cast(user_data); + lua_State* L = hook->L(); + + ul_find_lua_engine(L, uc); + hook->push_user_data(); + + lua_call(L, 2, 1); + + // TOS is a boolean indicating if the instruction was skipped. This follows + // the same rules as Lua, i.e. only `false` and `nil` are considered falsy. + int result = lua_toboolean(L, -1); + lua_pop(L, 1); + return result != 0; +} + +static void edge_generated_hook( + uc_engine* uc, uc_tb* cur_tb, uc_tb* prev_tb, void* user_data) +{ + auto hook = reinterpret_cast(user_data); + lua_State* L = hook->L(); + + // Push the callback function onto the stack. + get_callback(hook); + + // Push the arguments + ul_find_lua_engine(L, uc); + create_table_from_transaction_block(L, cur_tb); + create_table_from_transaction_block(L, prev_tb); + hook->push_user_data(); + + lua_call(L, 4, 0); +} + +static void tcg_opcode_hook(uc_engine* uc, uint64_t address, uint64_t arg1, + uint64_t arg2, uint32_t size, void* user_data) +{ + auto hook = reinterpret_cast(user_data); + lua_State* L = hook->L(); + + // Push the callback function onto the stack. + get_callback(hook); + + // Push the arguments + ul_find_lua_engine(L, uc); + lua_pushinteger(L, static_cast(address)); + lua_pushinteger(L, static_cast(arg1)); + lua_pushinteger(L, static_cast(arg2)); + lua_pushinteger(L, static_cast(size)); + hook->push_user_data(); + + lua_call(L, 7, 0); +} + +static void arm64_cp_reg_to_lua_table(lua_State* L, const uc_arm64_cp_reg* reg) +{ + lua_createtable(L, 0, 6); + lua_pushinteger(L, reg->crn); + lua_setfield(L, -1, "crn"); + lua_pushinteger(L, reg->crm); + lua_setfield(L, -1, "crm"); + lua_pushinteger(L, reg->op0); + lua_setfield(L, -1, "op0"); + lua_pushinteger(L, reg->op1); + lua_setfield(L, -1, "op1"); + lua_pushinteger(L, reg->op2); + lua_setfield(L, -1, "op2"); + lua_pushinteger(L, reg->val); + lua_setfield(L, -1, "val"); +} + +static uint32_t arm64_sys_hook(uc_engine* uc, uc_arm64_reg reg, + const uc_arm64_cp_reg* cp_reg, void* user_data) +{ + auto hook = reinterpret_cast(user_data); + lua_State* L = hook->L(); + + // Push the callback function onto the stack. + get_callback(hook); + + // Push the arguments + ul_find_lua_engine(L, uc); + lua_pushinteger(L, static_cast(reg)); + arm64_cp_reg_to_lua_table(L, cp_reg); + hook->push_user_data(); + + lua_call(L, 3, 1); + + int result = lua_toboolean(L, -1); + return result ? 1 : 0; +} + +#endif // UC_API_MAJOR >= 2 + static void* get_c_callback_for_hook_type(int hook_type, int insn_code) { switch (hook_type) { @@ -237,12 +345,26 @@ static void* get_c_callback_for_hook_type(int hook_type, int insn_code) return (void*)code_hook; case UC_HOOK_INSN: - /* TODO (dargueta): Support other architectures beside X86. */ - if (insn_code == UC_X86_INS_IN) - return (void*)port_in_hook; - if (insn_code == UC_X86_INS_OUT) - return (void*)port_out_hook; - return (void*)code_hook; + switch (insn_code) { + case UC_X86_INS_IN: + return reinterpret_cast(port_in_hook); + case UC_X86_INS_OUT: + return reinterpret_cast(port_out_hook); + case UC_X86_INS_SYSCALL: + case UC_X86_INS_SYSENTER: + return reinterpret_cast(generic_hook_with_no_arguments); +#if UC_API_MAJOR >= 2 + case UC_X86_INS_CPUID: + return reinterpret_cast(cpuid_hook); + case UC_ARM64_INS_MRS: + case UC_ARM64_INS_MSR: + case UC_ARM64_INS_SYS: + case UC_ARM64_INS_SYSL: + return reinterpret_cast(arm64_sys_hook); +#endif + default: + return (void*)code_hook; + } case UC_HOOK_MEM_FETCH: case UC_HOOK_MEM_READ: @@ -265,6 +387,13 @@ static void* get_c_callback_for_hook_type(int hook_type, int insn_code) case UC_HOOK_MEM_WRITE_UNMAPPED: return (void*)invalid_mem_access_hook; +#if UC_API_MAJOR >= 2 + case UC_HOOK_EDGE_GENERATED: + return (void*)edge_generated_hook; + case UC_HOOK_TCG_OPCODE: + return (void*)tcg_opcode_hook; +#endif // UC_API_MAJOR >= 2 + default: return nullptr; } @@ -340,7 +469,7 @@ int ul_hook_add(lua_State* L) if (error != UC_ERR_OK) { engine_object->remove_hook(hook_info); - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); } hook_info->set_hook_handle(hook_handle); diff --git a/src/memory.cpp b/src/memory.cpp index a58f0ad3..108f1574 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -17,7 +17,7 @@ int ul_mem_write(lua_State* L) uc_err error = uc_mem_write(engine, address, data, length); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -30,7 +30,7 @@ int ul_mem_read(lua_State* L) std::unique_ptr data(new char[length]); uc_err error = uc_mem_read(engine, address, data.get(), length); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); lua_pushlstring(L, data.get(), length); return 1; @@ -45,7 +45,7 @@ int ul_mem_map(lua_State* L) uc_err error = uc_mem_map(engine, address, size, perms); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -57,7 +57,7 @@ int ul_mem_unmap(lua_State* L) uc_err error = uc_mem_unmap(engine, address, size); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -70,7 +70,7 @@ int ul_mem_protect(lua_State* L) uc_err error = uc_mem_protect(engine, address, size, perms); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -84,7 +84,7 @@ int ul_mem_regions(lua_State* L) uc_err error = uc_mem_regions(engine, ®ions, &n_regions); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); lua_createtable(L, static_cast(n_regions), 0); for (uint32_t i = 0; i < n_regions; ++i) { diff --git a/src/registers_misc.cpp b/src/registers_misc.cpp index a0030b0b..39f117ca 100644 --- a/src/registers_misc.cpp +++ b/src/registers_misc.cpp @@ -219,7 +219,7 @@ int ul_reg_write(lua_State* L) uc_err error = uc_reg_write(engine, register_id, buffer); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -231,7 +231,7 @@ int ul_reg_write_as(lua_State* L) uc_err error = uc_reg_write(engine, register_id, reg.data_); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -259,7 +259,7 @@ int ul_reg_read(lua_State* L) uc_err error = uc_reg_read(engine, register_id, value_buffer); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); lua_pushinteger(L, *reinterpret_cast(value_buffer)); return 1; @@ -282,7 +282,7 @@ int ul_reg_read_as(lua_State* L) uc_err error = uc_reg_read(engine, register_id, value_buffer); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); Register register_obj(value_buffer, read_as_type); register_obj.push_to_lua(L); @@ -316,7 +316,7 @@ int ul_reg_write_batch(lua_State* L) uc_err error = uc_reg_write_batch(engine, register_ids.get(), p_values.get(), static_cast(n_registers)); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); return 0; } @@ -349,7 +349,7 @@ int ul_reg_read_batch(lua_State* L) uc_err error = uc_reg_read_batch(engine, register_ids.get(), value_pointers.get(), static_cast(n_registers)); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); for (size_t i = 0; i < n_registers; ++i) { lua_pushinteger(L, *reinterpret_cast(values[i])); @@ -381,7 +381,7 @@ int ul_reg_read_batch_as(lua_State* L) uc_err error = uc_reg_read_batch(engine, register_ids.get(), value_pointers.get(), static_cast(n_registers)); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); // Create the table we're going to return the register values in. The result // is a key-value mapping where the keys are the register IDs and the values diff --git a/src/template_data/basic_control_functions.lua b/src/template_data/basic_control_functions.lua new file mode 100644 index 00000000..d8e558dc --- /dev/null +++ b/src/template_data/basic_control_functions.lua @@ -0,0 +1,30 @@ +no_arguments_no_return = { + "exits_disable", + "exits_enable", + "flush_tlb", +} + +no_arguments_scalar_return = { + get_arch = { c_type = "int", lua_type = "integer" }, + get_cpu_model = { c_type = "int", lua_type = "integer" }, + get_exits_cnt = { c_type = "size_t", lua_type = "integer" }, + get_mode = { c_type = "int", lua_type = "integer" }, + get_page_size = { c_type = "uint32_t", lua_type = "integer" }, + get_timeout = { c_type = "uint64_t", lua_type = "number" }, +} + +scalar_arguments_no_return = { + remove_cache = { + { c_type = "uint64_t", lua_type = "integer" }, + { c_type = "uint64_t", lua_type = "integer" }, + }, + set_cpu_model = { + { c_type = "int", lua_type = "integer" }, + }, + set_page_size = { + { c_type = "uint32_t", lua_type = "integer" }, + }, +} + +-- tb ctl_request_cache(address) +-- ctl_set_exits(exits) diff --git a/src/register_types.lua b/src/template_data/register_types.lua similarity index 100% rename from src/register_types.lua rename to src/template_data/register_types.lua diff --git a/src/transaction.cpp b/src/transaction.cpp new file mode 100644 index 00000000..2109c6f4 --- /dev/null +++ b/src/transaction.cpp @@ -0,0 +1,19 @@ +#include + +#if UC_API_MAJOR >= 2 +#include "unicornlua/lua.hpp" +#include "unicornlua/transaction.hpp" + +void create_table_from_transaction_block(lua_State* L, const uc_tb* block) +{ + lua_createtable(L, 0, 3); + + lua_pushinteger(L, static_cast(block->pc)); + lua_setfield(L, -1, "pc"); + lua_pushinteger(L, static_cast(block->icount)); + lua_setfield(L, -1, "icount"); + lua_pushinteger(L, static_cast(block->size)); + lua_setfield(L, -1, "size"); +} + +#endif // UC_API_MAJOR diff --git a/src/unicorn.cpp b/src/unicorn.cpp index c5c5cd96..d87fa176 100644 --- a/src/unicorn.cpp +++ b/src/unicorn.cpp @@ -35,8 +35,18 @@ static int ul_create_unicornlua_version_table(lua_State* L) static int ul_arch_supported(lua_State* L) { - auto architecture = static_cast(luaL_checkinteger(L, -1)); - lua_pushboolean(L, uc_arch_supported(architecture)); + int is_supported = 0; + + // If the architecture is nil, return false. This allows code to easily + // determine if an architecture is supported without needing to check the + // Unicorn version AND assume that the Unicorn library was compiled with all + // available architectures. + if (!lua_isnil(L, 1)) { + auto architecture = static_cast(luaL_checkinteger(L, 1)); + is_supported = uc_arch_supported(architecture) ? 1 : 0; + } + + lua_pushboolean(L, is_supported); return 1; } @@ -48,7 +58,7 @@ static int ul_open(lua_State* L) uc_engine* engine; uc_err error = uc_open(architecture, mode, &engine); if (error != UC_ERR_OK) - return ul_crash_on_error(L, error); + ul_crash_on_error(L, error); // Create a block of memory for the engine userdata and then create the // UCLuaEngine in there using placement new. This way, Lua controls the diff --git a/src/utils.cpp b/src/utils.cpp index 921b12c7..1f5db92a 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,14 +1,23 @@ +#include + #include #include "unicornlua/lua.hpp" #include "unicornlua/utils.hpp" -int ul_crash_on_error(lua_State* L, uc_err error) +void ul_crash_on_error(lua_State* L, uc_err error) { const char* message = uc_strerror(error); lua_checkstack(L, 1); lua_pushstring(L, message); - return lua_error(L); + lua_error(L); +#if defined(__cpp_lib_unreachable) + std::unreachable(); +#elif defined(__GNUC__) // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif } void ul_create_weak_table(lua_State* L, const char* mode) diff --git a/tests/lua/imports.lua b/tests/lua/imports.lua index a22f1a65..53e1779a 100644 --- a/tests/lua/imports.lua +++ b/tests/lua/imports.lua @@ -7,6 +7,20 @@ describe("Ensure library loads don't crash.", function () it('[mips] require', function () require 'unicorn.mips_const' end) it('[sparc] require', function () require 'unicorn.sparc_const' end) it('[x86] require', function () require 'unicorn.x86_const' end) + + -- Unicorn 2.x only + describe("Unicorn 2.x tests only", function () + for _, arch in ipairs({"ppc", "riscv", "s390x", "tricore"}) do + it("[" .. arch .. "] require", function() + local uc = require "unicorn" + local v_major = uc:version() + + if v_major >= 2 then + require("unicorn." .. arch .. "_const") + end + end) + end + end) end) diff --git a/tools/ci/Makefile b/tools/ci/Makefile index c2410f2b..9d2fc143 100644 --- a/tools/ci/Makefile +++ b/tools/ci/Makefile @@ -1,9 +1,16 @@ UNICORN_DIRECTORY=__unicorn_repo +UNICORN_MAJOR_VERSION := $(firstword $(subst ., ,$(UNICORN_VERSION))) + +OS ?= $(shell uname -s) ifeq ($(OS),Windows_NT) - CI_UNICORN_INSTALL_RULE=install_unicorn__windows + CI_UNICORN_INSTALL_RULE=install_unicorn__windows else - CI_UNICORN_INSTALL_RULE=install_unicorn__unix + ifeq ($(UNICORN_MAJOR_VERSION),1) + CI_UNICORN_INSTALL_RULE=install_unicorn__unix__1 + else + CI_UNICORN_INSTALL_RULE=install_unicorn__unix__2 + endif endif @@ -17,12 +24,23 @@ $(UNICORN_DIRECTORY): install_unicorn: $(CI_UNICORN_INSTALL_RULE) -.PHONY: install_unicorn__unix -install_unicorn__unix: $(UNICORN_DIRECTORY) +# Installation for Unicorn 1.x +.PHONY: install_unicorn__unix__1 +install_unicorn__unix__1: $(UNICORN_DIRECTORY) + @echo "Installing Unicorn $(UNICORN_MAJOR_VERSION)" $(MAKE) -C $< cd $< && sudo ./make.sh install +# Installation for Unicorn 2.x +.PHONY: install_unicorn__unix__2 +install_unicorn__unix__2: $(UNICORN_DIRECTORY) + @echo "Installing Unicorn $(UNICORN_MAJOR_VERSION)" + cmake -S $(UNICORN_DIRECTORY) -B $(UNICORN_DIRECTORY)/build -DCMAKE_BUILD_TYPE=Release + $(MAKE) -C $(UNICORN_DIRECTORY)/build + sudo $(MAKE) -C $(UNICORN_DIRECTORY)/build install + + .PHONY: install_unicorn__windows install_unicorn__windows: python3 install-unicorn-windows.py $(UNICORN_VERSION) diff --git a/tools/render_template.lua b/tools/render_template.lua index 178d8af0..b27bb334 100644 --- a/tools/render_template.lua +++ b/tools/render_template.lua @@ -27,9 +27,11 @@ Arguments: The file to write the rendered template to.