Skip to content

Commit

Permalink
Add library version information and configuration safeguards (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
dargueta authored May 16, 2021
1 parent d252f93 commit 1ba2234
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 10 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Changes
=======

1.1.1 (2021-05-15)
------------------

New Features
~~~~~~~~~~~~

* Added a global constant to the ``unicorn`` module named ``UNICORNLUA_VERSION``.
This is a three-element table giving the major, minor, and patch versions of
the Lua binding.
* Added certain protections and better error messages in the ``configure`` script
to aid setting up your dev environment and debugging certain problems.

1.1.0 (2021-01-18)
------------------

Expand Down
74 changes: 70 additions & 4 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import configparser
import json
import logging
import os
import textwrap
import re
import shutil
import subprocess
import sys
Expand All @@ -24,8 +26,8 @@ DEFAULT_LUAROCKS_VERSION = CONFIG["luarocks"]["default_version"]
DEFAULT_LUA_VERSION = CONFIG["lua"]["default_version"]


# Detect if we're in a CI environment. This works for Travis, CircleCI, and
# AppVeyor. (AppVeyor uses both "True" and "true" hence this .lower() fiddling.)
# Detect if we're in a CI environment. This works for Travis, CircleCI, and AppVeyor.
# (AppVeyor uses both "True" and "true" hence this .lower() fiddling.)
IN_CI_ENVIRONMENT = os.getenv("CI", "").lower() == "true"


Expand Down Expand Up @@ -96,7 +98,9 @@ def parse_args():
" to the system's LuaRocks executable.",
)
parser.add_argument(
"--lua-headers", metavar="PATH", help="The path to the Lua headers files.",
"--lua-headers",
metavar="PATH",
help="The path to the Lua headers files.",
)
parser.add_argument(
"--lua-library",
Expand Down Expand Up @@ -131,6 +135,29 @@ def get_luarocks_version(luarocks_exe):
return version


def get_lua_header_version(header_dir):
file_path = os.path.join(header_dir, "lua.h")
if not os.path.exists(file_path):
raise ErrorExit(
"Directory doesn't exist or header file `lua.h` is missing: " + header_dir
)

LOG.debug("Determining Lua version from header file at %s", file_path)
with open(file_path, "r") as fd:
contents = fd.read()

match = re.search(r"LUA_VERSION_NUM\s+(\d+)", contents)
if not match:
raise ErrorExit("Couldn't determine version of Lua header at: " + file_path)

integer_version = int(match[1])
major_version = integer_version // 100
minor_version = integer_version % 100

LOG.debug("Lua header defines version as %d.%d.", major_version, minor_version)
return "%d.%d" % (major_version, minor_version)


def set_defaults_from_config(args):
result = vars(args)
if not args.venv_config:
Expand Down Expand Up @@ -184,6 +211,41 @@ def generate_cmake_parameters(settings, install_version, platform):
"IN_CI_ENVIRONMENT": "YES" if IN_CI_ENVIRONMENT else "NO",
}

if not settings["lua_library"] or not settings["lua_headers"]:
raise ErrorExit(
"The LUA_LIBRARIES and/or LUA_INCLUDE_DIR variables are empty. This usually"
" happens when Lua isn't installed locally or if your include paths aren't"
" set. You can either use a virtual environment (see tools/lua_env.py) or,"
" if you do have Lua already installed, you'll need to provide the include"
" and library paths to this script via the --lua-headers and --lua-library"
" options. If you're on a *NIX system, Lua headers are usually in"
" `/usr/include/lua<version>/` and the library is at"
" `/usr/lib/<architecture>/liblua<version>{.a, .so}`."
)

header_version = get_lua_header_version(settings["lua_headers"])

if settings["use_venv"]:
version_source = "Virtual environment"
else:
version_source = "OS"

# For LuaJIT, `short_version` is the version of LuaJIT, *not* Lua! Thus we have to
# special-case the version number check.
if is_luajit and header_version != "5.1":
raise ErrorExit(
"Mismatch between %s's LuaJIT and header version. LuaJIT implements 5.1,"
" but the header says %s. Either this is the wrong header, or this version"
" of LuaJIT implements an unexpected version of Lua. Header path: %s"
% (version_source, header_version, settings["lua_headers"])
)
elif not is_luajit and (header_version != short_version):
raise ErrorExit(
"Mismatch between Lua executable and header version. %s Lua version: %s;"
" header says %s. The path to the headers is likely wrong: %s"
% (version_source, short_version, header_version, settings["lua_headers"])
)

if settings["uc_lib"]:
values["UNICORN_LIBRARY"] = settings["uc_lib"]
if settings["uc_headers"]:
Expand Down Expand Up @@ -244,7 +306,11 @@ if __name__ == "__main__":
LOG.error("Killed.")
sys.exit(2)
except ErrorExit as exc:
LOG.error(str(exc))
if str(exc):
print()
multiline_message = textwrap.wrap(str(exc), width=80)
for line in multiline_message:
LOG.error(line)
sys.exit(1)
else:
sys.exit(0)
12 changes: 9 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,16 @@ Note: (This still works correctly if the library is compiled against Unicorn
*New in 1.1.0*


Global Functions
----------------
Globals
-------

These functions live in the ``unicorn`` namespace.
These live in the ``unicorn`` namespace.

``LUA_LIBRARY_VERSION``
~~~~~~~~~~~~~~~~~~~~~~~

This is a three-element table giving the major, minor, and patch versions of the
Lua binding.

``arch_supported(architecture)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
50 changes: 49 additions & 1 deletion include/unicornlua/unicornlua.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,55 @@
#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).
*/
#define UNICORNLUA_VERSION_MAJOR 1

/**
* The minor version number of this Lua library (second part, x.1.x).
*/
#define UNICORNLUA_VERSION_MINOR 1

#define UNICORNLUA_UNICORN_MAJOR_MINOR_PATCH ((UC_VERSION_MAJOR << 16) | (UC_VERSION_MINOR << 8) | UC_VERSION_EXTRA)
/**
* The patch version number of this Lua library (third part, x.x.1).
*/
#define UNICORNLUA_VERSION_PATCH 1

/**
* Create a 24-bit number from a release's major, minor, and patch numbers.
*
* You can use this for comparing a version number in your C/C++ code:
*
* #if UNICORNLUA_VERSION < MAKE_VERSION(1, 1, 0)
* // Executes on versions below 1.1.0
* #else
* // Executes on versions 1.1.0 and above (including 1.1.1)
* #endif
*/
#define MAKE_VERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))


/**
* The full version number of this Lua library, as an integer.
*
* This is a 24-bit number, where each version component is bit-shifted so that
* it occupies a single byte. The major version is the most-significant 8 bits,
* the minor version is the 8 bits below that, and the patch number is below
* that. Thus, release 1.10.16 would be represented by 0x010A10.
*/
#define UNICORNLUA_VERSION MAKE_VERSION(UNICORNLUA_VERSION_MAJOR, \
UNICORNLUA_VERSION_MINOR, \
UNICORNLUA_VERSION_PATCH)

/**
* The full version number of the Unicorn library this was compiled with, as an integer.
*
* The construction and semantics of this version number are the same as in
* @ref UNICORNLUA_VERSION.
*/
#define UNICORNLUA_UNICORN_MAJOR_MINOR_PATCH MAKE_VERSION(UC_VERSION_MAJOR, \
UC_VERSION_MINOR, \
UC_VERSION_EXTRA)

#endif /* INCLUDE_UNICORNLUA_UNICORNLUA_H_ */
31 changes: 29 additions & 2 deletions src/unicorn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#include "unicornlua/engine.h"
#include "unicornlua/lua.h"
#include "unicornlua/registers.h"
#include "unicornlua/unicornlua.h"
#include "unicornlua/utils.h"


static int ul_version(lua_State *L) {
static int ul_unicorn_version(lua_State *L) {
unsigned major, minor;

uc_version(&major, &minor);
Expand All @@ -17,6 +18,23 @@ static int ul_version(lua_State *L) {
}


// Create a three-element table that indicates the major, minor, and patch
// versions of this Lua binding.
static int ul_create_unicornlua_version_table(lua_State *L) {
lua_createtable(L, 3, 0);

lua_pushinteger(L, UNICORNLUA_VERSION_MAJOR);
lua_seti(L, -2, 1);

lua_pushinteger(L, UNICORNLUA_VERSION_MINOR);
lua_seti(L, -2, 2);

lua_pushinteger(L, UNICORNLUA_VERSION_PATCH);
lua_seti(L, -2, 3);
return 1;
}


static int ul_arch_supported(lua_State *L) {
auto architecture = static_cast<uc_arch>(luaL_checkinteger(L, -1));
lua_pushboolean(L, uc_arch_supported(architecture));
Expand Down Expand Up @@ -64,13 +82,22 @@ static const luaL_Reg kUnicornLibraryFunctions[] = {
{"arch_supported", ul_arch_supported},
{"open", ul_open},
{"strerror", ul_strerror},
{"version", ul_version},
{"version", ul_unicorn_version},
{nullptr, nullptr}
};


extern "C" UNICORN_EXPORT int luaopen_unicorn(lua_State *L) {
// Initialize the engine bits, such as the metatables that engine and context
// instances use.
ul_init_engines_lib(L);

// Create the main library table with all of the global functions in it.
luaL_newlib(L, kUnicornLibraryFunctions);

// Create a table in the library that contains the major, minor, and patch
// numbers of the Lua binding. These are positional values, not fields.
ul_create_unicornlua_version_table(L);
lua_setfield(L, -2, "LUA_LIBRARY_VERSION");
return 1;
}
17 changes: 17 additions & 0 deletions tests/lua/imports.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,20 @@ describe("Ensure library loads don't crash.", function ()
it('[sparc] require', function () require 'unicorn.sparc_const' end)
it('[x86] require', function () require 'unicorn.x86_const' end)
end)


describe('Ensure binding version number looks correct.', function ()
it('Check existence of version table', function()
local unicorn = require 'unicorn'
assert.is_not_nil(unicorn.LUA_LIBRARY_VERSION)
end)

it('Checks version table looks correct', function ()
local unicorn = require 'unicorn'
local major, minor, patch = table.unpack(unicorn.LUA_LIBRARY_VERSION)

assert.is_equal("number", type(major), 'Major version is borked')
assert.is_equal("number", type(minor), 'Minor version is borked')
assert.is_equal("number", type(patch), 'Patch version is borked')
end)
end)

0 comments on commit 1ba2234

Please sign in to comment.