[lldb/lua] Supplement Lua bindings for lldb module

Add necessary typemaps for Lua bindings, together with some other files.

Signed-off-by: Siger Yang <sigeryeung@gmail.com>

Reviewed By: tammela

Differential Revision: https://reviews.llvm.org/D108090
This commit is contained in:
Siger Yang 2021-10-12 22:08:05 +08:00
parent 40546cb381
commit 67f94e5a97
17 changed files with 950 additions and 2 deletions

View File

@ -51,6 +51,13 @@ if (LLDB_ENABLE_PYTHON)
CACHE STRING "Path where Python modules are installed, relative to install prefix")
endif ()
if (LLDB_ENABLE_LUA)
find_program(Lua_EXECUTABLE lua5.3)
set(LLDB_LUA_DEFAULT_RELATIVE_PATH "lib/lua/5.3")
set(LLDB_LUA_RELATIVE_PATH ${LLDB_LUA_DEFAULT_RELATIVE_PATH}
CACHE STRING "Path where Lua modules are installed, relative to install prefix")
endif ()
if (LLDB_ENABLE_PYTHON OR LLDB_ENABLE_LUA)
add_subdirectory(bindings)
endif ()
@ -94,6 +101,16 @@ if (LLDB_ENABLE_PYTHON)
finish_swig_python("lldb-python" "${lldb_python_bindings_dir}" "${lldb_python_target_dir}")
endif()
if (LLDB_ENABLE_LUA)
if(LLDB_BUILD_FRAMEWORK)
set(lldb_lua_target_dir "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/Resources/Lua")
else()
set(lldb_lua_target_dir "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${LLDB_LUA_RELATIVE_PATH}")
endif()
get_target_property(lldb_lua_bindings_dir swig_wrapper_lua BINARY_DIR)
finish_swig_lua("lldb-lua" "${lldb_lua_bindings_dir}" "${lldb_lua_target_dir}")
endif()
option(LLDB_INCLUDE_TESTS "Generate build targets for the LLDB unit tests." ${LLVM_INCLUDE_TESTS})
if(LLDB_INCLUDE_TESTS)
add_subdirectory(test)

View File

@ -17,3 +17,55 @@ add_custom_command(
add_custom_target(swig_wrapper_lua ALL DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/LLDBWrapLua.cpp
)
function(create_lua_package swig_target working_dir pkg_dir)
cmake_parse_arguments(ARG "NOINIT" "" "FILES" ${ARGN})
add_custom_command(TARGET ${swig_target} POST_BUILD VERBATIM
COMMAND ${CMAKE_COMMAND} -E make_directory ${pkg_dir}
WORKING_DIRECTORY ${working_dir})
endfunction()
function(finish_swig_lua swig_target lldb_lua_bindings_dir lldb_lua_target_dir)
add_custom_target(${swig_target} ALL VERBATIM
COMMAND ${CMAKE_COMMAND} -E make_directory ${lldb_lua_target_dir}
DEPENDS swig_wrapper_lua
COMMENT "LLDB Lua API")
if(LLDB_BUILD_FRAMEWORK)
set(LIBLLDB_SYMLINK_DEST "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/LLDB")
else()
set(LIBLLDB_SYMLINK_DEST "${LLVM_SHLIB_OUTPUT_INTDIR}/liblldb${CMAKE_SHARED_LIBRARY_SUFFIX}")
endif()
if(WIN32)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(LIBLLDB_SYMLINK_OUTPUT_FILE "_lldb_d.pyd")
else()
set(LIBLLDB_SYMLINK_OUTPUT_FILE "_lldb.pyd")
endif()
else()
set(LIBLLDB_SYMLINK_OUTPUT_FILE "lldb.so")
endif()
create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST}
${lldb_lua_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE})
set(lldb_lua_library_target "${swig_target}-library")
add_custom_target(${lldb_lua_library_target})
add_dependencies(${lldb_lua_library_target} ${swig_target})
# Ensure we do the Lua post-build step when building lldb.
add_dependencies(lldb ${swig_target})
if(LLDB_BUILD_FRAMEWORK)
set(LLDB_LUA_INSTALL_PATH ${LLDB_FRAMEWORK_INSTALL_DIR}/LLDB.framework/Resources/Python)
else()
set(LLDB_LUA_INSTALL_PATH ${LLDB_LUA_RELATIVE_PATH})
endif()
install(DIRECTORY ${lldb_lua_target_dir}/
DESTINATION ${LLDB_LUA_INSTALL_PATH}
COMPONENT ${lldb_lua_library_target})
set(lldb_lua_library_install_target "install-${lldb_lua_library_target}")
if (NOT LLVM_ENABLE_IDE)
add_llvm_install_targets(${lldb_lua_library_install_target}
COMPONENT ${lldb_lua_library_target}
DEPENDS ${lldb_lua_library_target})
endif()
endfunction()

View File

@ -12,7 +12,7 @@
// Primitive integer mapping
%typemap(in,checkfn="lua_isinteger") TYPE
%{ $1 = (TYPE)lua_tointeger(L, $input); %}
%{ $1 = ($type)lua_tointeger(L, $input); %}
%typemap(in,checkfn="lua_isinteger") const TYPE&($basetype temp)
%{ temp=($basetype)lua_tointeger(L,$input); $1=&temp;%}
%typemap(out) TYPE
@ -54,6 +54,7 @@ LLDB_NUMBER_TYPEMAP(signed long);
LLDB_NUMBER_TYPEMAP(long long);
LLDB_NUMBER_TYPEMAP(unsigned long long);
LLDB_NUMBER_TYPEMAP(signed long long);
LLDB_NUMBER_TYPEMAP(enum SWIGTYPE);
%apply unsigned long { size_t };
%apply const unsigned long & { const size_t & };
@ -86,6 +87,9 @@ LLDB_NUMBER_TYPEMAP(signed long long);
// as char data instead of byte data.
%typemap(in) (void *char_buf, size_t size) = (char *dst, size_t dst_len);
// Also SBProcess::ReadMemory.
%typemap(in) (void *buf, size_t size) = (char *dst, size_t dst_len);
// Return the char buffer. Discarding any previous return result
%typemap(argout) (char *dst, size_t dst_len) {
lua_pop(L, 1); // Blow away the previous result
@ -102,4 +106,211 @@ LLDB_NUMBER_TYPEMAP(signed long long);
// as char data instead of byte data.
%typemap(argout) (void *char_buf, size_t size) = (char *dst, size_t dst_len);
// Also SBProcess::ReadMemory.
%typemap(argout) (void *buf, size_t size) = (char *dst, size_t dst_len);
//===----------------------------------------------------------------------===//
// Typemap for handling a snprintf-like API like SBThread::GetStopDescription.
%typemap(in) (char *dst_or_null, size_t dst_len) {
$2 = luaL_checkinteger(L, $input);
if ($2 <= 0) {
return luaL_error(L, "Positive integer expected");
}
$1 = (char *)malloc($2);
}
%typemap(argout) (char *dst_or_null, size_t dst_len) {
lua_pop(L, 1); // Blow away the previous result
lua_pushlstring(L, (const char *)$1, $result);
free($1);
// SWIG_arg was already incremented
}
//===----------------------------------------------------------------------===//
// Typemap for handling SBModule::GetVersion
%typemap(in) (uint32_t *versions, uint32_t num_versions) {
$2 = 99;
$1 = (uint32_t *)malloc(sizeof(uint32_t) * $2);
}
%typemap(argout) (uint32_t *versions, uint32_t num_versions) {
uint32_t count = result;
if (count >= $2)
count = $2;
lua_newtable(L);
int i = 0;
while (i++ < count) {
lua_pushinteger(L, $1[i - 1]);
lua_seti(L, -2, i);
}
SWIG_arg++;
free($1);
}
//===----------------------------------------------------------------------===//
// Typemap for handling SBDebugger::SetLoggingCallback
%typemap(in) (lldb::LogOutputCallback log_callback, void *baton) {
$1 = LLDBSwigLuaCallLuaLogOutputCallback;
$2 = (void *)L;
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_settop(L, 2);
lua_pushlightuserdata(L, (void *)&LLDBSwigLuaCallLuaLogOutputCallback);
lua_insert(L, 2);
lua_settable(L, LUA_REGISTRYINDEX);
}
//===----------------------------------------------------------------------===//
// Typemap for handling SBEvent::SBEvent(uint32_t event, const char *cstr, uint32_t cstr_len)
%typemap(in) (const char *cstr, uint32_t cstr_len) {
$1 = (char *)luaL_checklstring(L, $input, (size_t *)&$2);
}
// Typemap for handling SBProcess::PutSTDIN
%typemap(in) (const char *src, size_t src_len) {
$1 = (char *)luaL_checklstring(L, $input, &$2);
}
// Typemap for handling SBProcess::WriteMemory, SBTarget::GetInstructions...
%typemap(in) (const void *buf, size_t size),
(const void *data, size_t data_len) {
$1 = (void *)luaL_checklstring(L, $input, &$2);
}
//===----------------------------------------------------------------------===//
// Typemap for handling char ** in SBTarget::LaunchSimple, SBTarget::Launch...
// It should accept a Lua table of strings, for stuff like "argv" and "envp".
%typemap(in) char ** {
if (lua_istable(L, $input)) {
size_t size = lua_rawlen(L, $input);
$1 = (char **)malloc((size + 1) * sizeof(char *));
int i = 0, j = 0;
while (i++ < size) {
lua_rawgeti(L, $input, i);
if (!lua_isstring(L, -1)) {
// if current element cannot be converted to string, raise an error
lua_pop(L, 1);
return luaL_error(L, "List should only contain strings");
}
$1[j++] = (char *)lua_tostring(L, -1);
lua_pop(L, 1);
}
$1[j] = 0;
} else if (lua_isnil(L, $input)) {
// "nil" is also acceptable, equivalent as an empty table
$1 = NULL;
} else {
return luaL_error(L, "A list of strings expected");
}
}
%typemap(freearg) char ** {
free((char *) $1);
}
%typecheck(SWIG_TYPECHECK_STRING_ARRAY) char ** {
$1 = (lua_istable(L, $input) || lua_isnil(L, $input));
}
//===----------------------------------------------------------------------===//
// Typemap for file handles (e.g. used in SBDebugger::SetOutputFile)
%typemap(in) lldb::FileSP {
luaL_Stream *p = (luaL_Stream *)luaL_checkudata(L, $input, LUA_FILEHANDLE);
lldb::FileSP file_sp;
file_sp = std::make_shared<lldb_private::NativeFile>(p->f, false);
if (!file_sp->IsValid())
return luaL_error(L, "Invalid file");
$1 = file_sp;
}
%typecheck(SWIG_TYPECHECK_POINTER) lldb::FileSP {
$1 = (lua_isuserdata(L, $input)) &&
(luaL_testudata(L, $input, LUA_FILEHANDLE) != nullptr);
}
// Typemap for file handles (e.g. used in SBDebugger::GetOutputFileHandle)
%typemap(out) lldb::FileSP {
lldb::FileSP &sp = $1;
if (sp && sp->IsValid()) {
luaL_Stream *p = (luaL_Stream *)lua_newuserdata(L, sizeof(luaL_Stream));
p->closef = &LLDBSwigLuaCloseFileHandle;
p->f = sp->GetStream();
luaL_setmetatable(L, LUA_FILEHANDLE);
SWIG_arg++;
}
}
//===----------------------------------------------------------------------===//
// Typemap for SBData::CreateDataFromUInt64Array, SBData::SetDataFromUInt64Array ...
%typemap(in) (uint64_t* array, size_t array_len),
(uint32_t* array, size_t array_len),
(int64_t* array, size_t array_len),
(int32_t* array, size_t array_len),
(double* array, size_t array_len) {
if (lua_istable(L, $input)) {
// It should accept a table of numbers.
$2 = lua_rawlen(L, $input);
$1 = ($1_ltype)malloc(($2) * sizeof($*1_type));
int i = 0, j = 0;
while (i++ < $2) {
lua_rawgeti(L, $input, i);
if (!lua_isnumber(L, -1)) {
// if current element cannot be converted to number, raise an error
lua_pop(L, 1);
return luaL_error(L, "List should only contain numbers");
}
$1[j++] = ($*1_ltype)lua_tonumber(L, -1);
lua_pop(L, 1);
}
} else if (lua_isnil(L, $input)) {
// "nil" is also acceptable, equivalent as an empty table
$1 = NULL;
$2 = 0;
} else {
// else raise an error
return luaL_error(L, "A list of numbers expected.");
}
}
%typemap(freearg) (uint64_t* array, size_t array_len),
(uint32_t* array, size_t array_len),
(int64_t* array, size_t array_len),
(int32_t* array, size_t array_len),
(double* array, size_t array_len) {
free($1);
}
//===----------------------------------------------------------------------===//
// Typemap for SBCommandReturnObject::PutCString
%typemap(in) (const char *string, int len) {
if (lua_isnil(L, $input)) {
$1 = NULL;
$2 = 0;
}
else {
$1 = (char *)luaL_checklstring(L, $input, (size_t *)&$2);
}
}
//===----------------------------------------------------------------------===//

View File

@ -6,6 +6,19 @@ PushSBClass(lua_State* L, T* obj);
%}
%runtime %{
#ifdef __cplusplus
extern "C" {
#endif
void LLDBSwigLuaCallLuaLogOutputCallback(const char *str, void *baton);
int LLDBSwigLuaCloseFileHandle(lua_State *L);
#ifdef __cplusplus
}
#endif
%}
%wrapper %{
// This function is called from Lua::CallBreakpointCallback
@ -88,5 +101,20 @@ LLDBSwigLuaWatchpointCallbackFunction
return stop;
}
SWIGEXPORT void
LLDBSwigLuaCallLuaLogOutputCallback(const char *str, void *baton) {
lua_State *L = (lua_State *)baton;
lua_pushlightuserdata(L, (void *)&LLDBSwigLuaCallLuaLogOutputCallback);
lua_gettable(L, LUA_REGISTRYINDEX);
// FIXME: There's no way to report errors back to the user
lua_pushstring(L, str);
lua_pcall(L, 1, 0, 0);
}
int LLDBSwigLuaCloseFileHandle(lua_State *L) {
return luaL_error(L, "You cannot close a file handle used by lldb.");
}
%}

View File

@ -17,6 +17,10 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "../bindings/lua/lua-swigsafecast.swig"
// required headers for typemaps
#include "lldb/Host/File.h"
using namespace lldb_private;
using namespace lldb;
%}

View File

@ -4,3 +4,4 @@ _ZN12lldb_private*
_ZNK12lldb_private*
init_lld*
PyInit__lldb*
luaopen_lldb*

View File

@ -2,3 +2,4 @@ _ZN4lldb*
_ZNK4lldb*
init_lld*
PyInit__lldb*
luaopen_lldb*

View File

@ -19,6 +19,8 @@ config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
config.target_triple = "@TARGET_TRIPLE@"
config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@"
config.python_executable = "@Python3_EXECUTABLE@"
config.lua_executable = "@Lua_EXECUTABLE@"
config.lua_test_entry = "TestLuaAPI.py"
config.dotest_args_str = "@LLDB_DOTEST_ARGS@"
config.lldb_enable_python = @LLDB_ENABLE_PYTHON@
config.dotest_lit_args_str = None

View File

@ -50,11 +50,17 @@ class LLDBTest(TestFormat):
# build with.
executable = test.config.python_executable
isLuaTest = testFile == test.config.lua_test_entry
# On Windows, the system does not always correctly interpret
# shebang lines. To make sure we can execute the tests, add
# python exe as the first parameter of the command.
cmd = [executable] + self.dotest_cmd + [testPath, '-p', testFile]
if isLuaTest:
luaExecutable = test.config.lua_executable
cmd.extend(['--env', 'LUA_EXECUTABLE=%s' % luaExecutable])
timeoutInfo = None
try:
out, err, exitCode = lit.util.executeCommand(

View File

@ -0,0 +1,3 @@
C_SOURCES := main.c
include Makefile.rules

View File

@ -0,0 +1,52 @@
_T = require('lua_lldb_test').create_test('TestBreakpointAPI')
function _T:TestBreakpointIsValid()
local target = self:create_target()
local breakpoint = target:BreakpointCreateByName('AFunction', 'a.out')
assertTrue(breakpoint:IsValid() and breakpoint:GetNumLocations() == 1)
local did_delete = target:BreakpointDelete(breakpoint:GetID())
assertTrue(did_delete)
local del_bkpt = target:FindBreakpointByID(breakpoint:GetID())
assertFalse(del_bkpt:IsValid())
assertFalse(breakpoint:IsValid())
end
function _T:TestTargetDelete()
local target = self:create_target()
local breakpoint = target:BreakpointCreateByName('AFunction', 'a.out')
assertTrue(breakpoint:IsValid() and breakpoint:GetNumLocations() == 1)
local location = breakpoint:GetLocationAtIndex(0)
assertTrue(location:IsValid())
assertEquals(target, breakpoint:GetTarget())
assertTrue(self.debugger:DeleteTarget(target))
assertFalse(breakpoint:IsValid())
assertFalse(location:IsValid())
end
function _T:TestBreakpointHitCount()
local target = self:create_target()
local breakpoint = target:BreakpointCreateByName('BFunction', 'a.out')
assertTrue(breakpoint:IsValid() and breakpoint:GetNumLocations() == 1)
breakpoint:SetAutoContinue(true)
target:LaunchSimple(nil, nil, nil)
assertEquals(breakpoint:GetHitCount(), 100)
end
function _T:TestBreakpointFrame()
local target = self:create_target()
local breakpoint = target:BreakpointCreateByName('main', 'a.out')
assertTrue(breakpoint:IsValid() and breakpoint:GetNumLocations() == 1)
local process = target:LaunchSimple({ 'arg1', 'arg2' }, nil, nil)
local thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint)
assertNotNil(thread)
assertTrue(thread:IsValid())
local frame = thread:GetFrameAtIndex(0)
assertTrue(frame:IsValid())
local error = lldb.SBError()
local var_argc = frame:FindVariable('argc')
local var_argc_value = var_argc:GetValueAsSigned(error, 0)
assertTrue(error:Success())
assertEquals(var_argc_value, 3)
end
os.exit(_T:run())

View File

@ -0,0 +1,99 @@
_T = require('lua_lldb_test').create_test('TestComprehensive')
function _T:Test0_CreateTarget()
self.target = self:create_target()
assertTrue(self.target:IsValid())
end
function _T:Test1_Breakpoint()
self.main_bp = self.target:BreakpointCreateByName('main', 'a.out')
self.loop_bp = self.target:BreakpointCreateByLocation('main.c', 28)
assertTrue(self.main_bp:IsValid() and self.main_bp:GetNumLocations() == 1)
assertTrue(self.loop_bp:IsValid() and self.loop_bp:GetNumLocations() == 1)
end
function _T:Test2_Launch()
local error = lldb.SBError()
self.args = { 'arg' }
self.process = self.target:Launch(
self.debugger:GetListener(),
self.args,
nil,
nil,
self.output,
nil,
nil,
0,
false,
error
)
assertTrue(error:Success())
assertTrue(self.process:IsValid())
end
function _T:Test3_BreakpointFindVariables()
-- checking "argc" value
local thread = get_stopped_thread(self.process, lldb.eStopReasonBreakpoint)
assertNotNil(thread)
assertTrue(thread:IsValid())
local frame = thread:GetFrameAtIndex(0)
assertTrue(frame:IsValid())
local error = lldb.SBError()
local var_argc = frame:FindVariable('argc')
assertTrue(var_argc:IsValid())
local var_argc_value = var_argc:GetValueAsSigned(error, 0)
assertTrue(error:Success())
assertEquals(var_argc_value, 2)
-- checking "inited" value
local continue = self.process:Continue()
assertTrue(continue:Success())
thread = get_stopped_thread(self.process, lldb.eStopReasonBreakpoint)
assertNotNil(thread)
assertTrue(thread:IsValid())
frame = thread:GetFrameAtIndex(0)
assertTrue(frame:IsValid())
error = lldb.SBError()
local var_inited = frame:FindVariable('inited')
assertTrue(var_inited:IsValid())
self.var_inited = var_inited
local var_inited_value = var_inited:GetValueAsUnsigned(error, 0)
assertTrue(error:Success())
assertEquals(var_inited_value, 0xDEADBEEF)
end
function _T:Test3_RawData()
local error = lldb.SBError()
local address = self.var_inited:GetAddress()
assertTrue(address:IsValid())
local size = self.var_inited:GetByteSize()
local raw_data = self.process:ReadMemory(address:GetOffset(), size, error)
assertTrue(error:Success())
local data_le = lldb.SBData.CreateDataFromUInt32Array(lldb.eByteOrderLittle, 1, {0xDEADBEEF})
local data_be = lldb.SBData.CreateDataFromUInt32Array(lldb.eByteOrderBig, 1, {0xDEADBEEF})
assertTrue(data_le:GetUnsignedInt32(error, 0) == 0xDEADBEEF or data_be:GetUnsignedInt32(error, 0) == 0xDEADBEEF)
assertTrue(raw_data == "\xEF\xBE\xAD\xDE" or raw_data == "\xDE\xAD\xBE\xEF")
end
function _T:Test4_ProcessExit()
self.loop_bp:SetAutoContinue(true)
local continue = self.process:Continue()
assertTrue(continue:Success())
assertTrue(self.process:GetExitStatus() == 0)
end
function _T:Test5_FileOutput()
local f = io.open(self.output, 'r')
assertEquals(
read_file_non_empty_lines(f),
{
self.exe,
table.unpack(self.args),
'I am a function.',
'sum = 5050'
}
)
f:close()
end
os.exit(_T:run())

View File

@ -0,0 +1,37 @@
_T = require('lua_lldb_test').create_test('TestFileHandle')
function _T:TestLegacyFileOutScript()
local f = io.open(self.output, 'w')
self.debugger:SetOutputFile(f)
self:handle_command('script print(1+1)')
self.debugger:GetOutputFileHandle():write('FOO\n')
self.debugger:GetOutputFileHandle():flush()
f:close()
f = io.open(self.output, 'r')
assertEquals(read_file_non_empty_lines(f), {'2', 'FOO'})
f:close()
end
function _T:TestLegacyFileOut()
local f = io.open(self.output, 'w')
self.debugger:SetOutputFile(f)
self:handle_command('p/x 3735928559', false)
f:close()
f = io.open(self.output, 'r')
assertStrContains(f:read('*l'), 'deadbeef')
f:close()
end
function _T:TestLegacyFileErr()
local f = io.open(self.output, 'w')
self.debugger:SetErrorFile(f)
self:handle_command('lol', false)
f = io.open(self.output, 'r')
assertStrContains(f:read('*l'), 'is not a valid command')
f:close()
end
os.exit(_T:run())

View File

@ -0,0 +1,186 @@
"""
Test Lua API wrapper
"""
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import subprocess
def to_string(b):
"""Return the parameter as type 'str', possibly encoding it.
In Python2, the 'str' type is the same as 'bytes'. In Python3, the
'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is
distinct.
"""
if isinstance(b, str):
# In Python2, this branch is taken for types 'str' and 'bytes'.
# In Python3, this branch is taken only for 'str'.
return b
if isinstance(b, bytes):
# In Python2, this branch is never taken ('bytes' is handled as 'str').
# In Python3, this is true only for 'bytes'.
try:
return b.decode('utf-8')
except UnicodeDecodeError:
# If the value is not valid Unicode, return the default
# repr-line encoding.
return str(b)
# By this point, here's what we *don't* have:
#
# - In Python2:
# - 'str' or 'bytes' (1st branch above)
# - In Python3:
# - 'str' (1st branch above)
# - 'bytes' (2nd branch above)
#
# The last type we might expect is the Python2 'unicode' type. There is no
# 'unicode' type in Python3 (all the Python3 cases were already handled). In
# order to get a 'str' object, we need to encode the 'unicode' object.
try:
return b.encode('utf-8')
except AttributeError:
raise TypeError('not sure how to convert %s to %s' % (type(b), str))
class ExecuteCommandTimeoutException(Exception):
def __init__(self, msg, out, err, exitCode):
assert isinstance(msg, str)
assert isinstance(out, str)
assert isinstance(err, str)
assert isinstance(exitCode, int)
self.msg = msg
self.out = out
self.err = err
self.exitCode = exitCode
# Close extra file handles on UNIX (on Windows this cannot be done while
# also redirecting input).
kUseCloseFDs = not (platform.system() == 'Windows')
def executeCommand(command, cwd=None, env=None, input=None, timeout=0):
"""Execute command ``command`` (list of arguments or string) with.
* working directory ``cwd`` (str), use None to use the current
working directory
* environment ``env`` (dict), use None for none
* Input to the command ``input`` (str), use string to pass
no input.
* Max execution time ``timeout`` (int) seconds. Use 0 for no timeout.
Returns a tuple (out, err, exitCode) where
* ``out`` (str) is the standard output of running the command
* ``err`` (str) is the standard error of running the command
* ``exitCode`` (int) is the exitCode of running the command
If the timeout is hit an ``ExecuteCommandTimeoutException``
is raised.
"""
if input is not None:
input = to_bytes(input)
p = subprocess.Popen(command, cwd=cwd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env, close_fds=kUseCloseFDs)
timerObject = None
# FIXME: Because of the way nested function scopes work in Python 2.x we
# need to use a reference to a mutable object rather than a plain
# bool. In Python 3 we could use the "nonlocal" keyword but we need
# to support Python 2 as well.
hitTimeOut = [False]
try:
if timeout > 0:
def killProcess():
# We may be invoking a shell so we need to kill the
# process and all its children.
hitTimeOut[0] = True
killProcessAndChildren(p.pid)
timerObject = threading.Timer(timeout, killProcess)
timerObject.start()
out, err = p.communicate(input=input)
exitCode = p.wait()
finally:
if timerObject != None:
timerObject.cancel()
# Ensure the resulting output is always of string type.
out = to_string(out)
err = to_string(err)
if hitTimeOut[0]:
raise ExecuteCommandTimeoutException(
msg='Reached timeout of {} seconds'.format(timeout),
out=out,
err=err,
exitCode=exitCode
)
# Detect Ctrl-C in subprocess.
if exitCode == -signal.SIGINT:
raise KeyboardInterrupt
return out, err, exitCode
class TestLuaAPI(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
def get_tests(self):
tests = []
for filename in os.listdir():
# Ignore dot files and excluded tests.
if filename.startswith('.'):
continue
# Ignore files that don't start with 'Test'.
if not filename.startswith('Test'):
continue
if not os.path.isdir(filename):
base, ext = os.path.splitext(filename)
if ext == '.lua':
tests.append(filename)
return tests
def test_lua_api(self):
if "LUA_EXECUTABLE" not in os.environ or len(os.environ["LUA_EXECUTABLE"]) == 0:
self.skipTest("Lua API tests could not find Lua executable.")
return
lua_executable = os.environ["LUA_EXECUTABLE"]
self.build()
test_exe = self.getBuildArtifact("a.out")
test_output = self.getBuildArtifact("output")
test_input = self.getBuildArtifact("input")
lua_lldb_cpath = "%s/lua/5.3/?.so" % configuration.lldb_libs_dir
lua_prelude = "package.cpath = '%s;' .. package.cpath" % lua_lldb_cpath
lua_env = {
"TEST_EXE": os.path.join(self.getBuildDir(), test_exe),
"TEST_OUTPUT": os.path.join(self.getBuildDir(), test_output),
"TEST_INPUT": os.path.join(self.getBuildDir(), test_input)
}
for lua_test in self.get_tests():
cmd = [lua_executable] + ["-e", lua_prelude] + [lua_test]
out, err, exitCode = executeCommand(cmd, env=lua_env)
# Redirect Lua output
print(out)
print(err, file=sys.stderr)
self.assertTrue(
exitCode == 0,
"Lua test '%s' failure." % lua_test
)

View File

@ -0,0 +1,59 @@
_T = require('lua_lldb_test').create_test('TestProcessAPI')
function _T:TestProcessLaunchSimple()
local target = self:create_target()
local args = { 'arg1', 'arg2', 'arg3' }
local process = target:LaunchSimple(
-- argv
args,
-- envp
nil,
-- working directory
nil
)
assertTrue(process:IsValid())
local stdout = process:GetSTDOUT(1000)
assertEquals(split_lines(stdout), {self.exe, table.unpack(args)})
end
function _T:TestProcessLaunch()
local target = self:create_target()
local args = { 'arg1', 'arg2', 'arg3' }
local error = lldb.SBError()
local f = io.open(self.output, 'w')
f:write()
f:close()
local process = target:Launch(
-- listener
self.debugger:GetListener(),
-- argv
args,
-- envp
nil,
-- stdin
nil,
-- stdout
self.output,
-- stderr
nil,
-- working directory
nil,
-- launch flags
0,
-- stop at entry
true,
-- error
error
)
assertTrue(error:Success())
assertTrue(process:IsValid())
local threads = get_stopped_threads(process, lldb.eStopReasonSignal)
assertTrue(#threads ~= 0)
local continue = process:Continue()
assertTrue(continue:Success())
local f = io.open(self.output, 'r')
assertEquals(read_file_non_empty_lines(f), {self.exe, table.unpack(args)})
f:close()
end
os.exit(_T:run())

View File

@ -0,0 +1,155 @@
-- Make lldb available in global
lldb = require('lldb')
-- Global assertion functions
function assertTrue(x)
if not x then error('assertTrue failure') end
end
function assertFalse(x)
if x then error('assertNotNil failure') end
end
function assertNotNil(x)
if x == nil then error('assertNotNil failure') end
end
function assertEquals(x, y)
if type(x) == 'table' and type(y) == 'table' then
for k, _ in pairs(x) do
assertEquals(x[k], y[k])
end
elseif type(x) ~= type(y) then
error('assertEquals failure')
elseif x ~= y then
error('assertEquals failure')
end
end
function assertStrContains(x, y)
if not string.find(x, y, 1, true) then
error('assertStrContains failure')
end
end
-- Global helper functions
function read_file_non_empty_lines(f)
local lines = {}
while true do
local line = f:read('*l')
if not line then break end
if line ~= '\n' then table.insert(lines, line) end
end
return lines
end
function split_lines(str)
local lines = {}
for line in str:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
return lines
end
function get_stopped_threads(process, reason)
local threads = {}
for i = 0, process:GetNumThreads() - 1 do
local t = process:GetThreadAtIndex(i)
if t:IsValid() and t:GetStopReason() == reason then
table.insert(threads, t)
end
end
return threads
end
function get_stopped_thread(process, reason)
local threads = get_stopped_threads(process, reason)
if #threads ~= 0 then return threads[1]
else return nil end
end
-- Test helper
local _M = {}
local _m = {}
local _mt = { __index = _m }
function _M.create_test(name, exe, output, input)
print('[lldb/lua] Create test ' .. name)
exe = exe or os.getenv('TEST_EXE')
output = output or os.getenv('TEST_OUTPUT')
input = input or os.getenv('TEST_INPUT')
lldb.SBDebugger.Initialize()
local debugger = lldb.SBDebugger.Create()
-- Ensure that debugger is created
assertNotNil(debugger)
assertTrue(debugger:IsValid())
debugger:SetAsync(false)
local lua_language = debugger:GetScriptingLanguage('lua')
assertNotNil(lua_language)
debugger:SetScriptLanguage(lua_language)
local test = setmetatable({
output = output,
input = input,
name = name,
exe = exe,
debugger = debugger
}, _mt)
_G[name] = test
return test
end
function _m:create_target(exe)
local target
if not exe then exe = self.exe end
target = self.debugger:CreateTarget(exe)
-- Ensure that target is created
assertNotNil(target)
assertTrue(target:IsValid())
return target
end
function _m:handle_command(command, collect)
if collect == nil then collect = true end
if collect then
local ret = lldb.SBCommandReturnObject()
local interpreter = self.debugger:GetCommandInterpreter()
assertTrue(interpreter:IsValid())
interpreter:HandleCommand(command, ret)
self.debugger:GetOutputFile():Flush()
self.debugger:GetErrorFile():Flush()
assertTrue(ret:Succeeded())
return ret:GetOutput()
else
self.debugger:HandleCommand(command)
self.debugger:GetOutputFile():Flush()
self.debugger:GetErrorFile():Flush()
end
end
function _m:run()
local tests = {}
for k, v in pairs(self) do
if string.sub(k, 1, 4) == 'Test' then
table.insert(tests, k)
end
end
table.sort(tests)
for _, t in ipairs(tests) do
print('[lldb/lua] Doing test ' .. self.name .. ' - ' .. t)
local success = xpcall(self[t], function(e)
print(debug.traceback())
end, self)
if not success then
print('[lldb/lua] Failure in test ' .. self.name .. ' - ' .. t)
return 1
end
end
return 0
end
return _M

View File

@ -0,0 +1,35 @@
#include <stdio.h>
void BFunction()
{
}
void AFunction()
{
printf("I am a function.\n");
}
int main(int argc, const char *argv[])
{
int inited = 0xDEADBEEF;
int sum = 0;
if(argc > 1)
{
for(int i = 0; i < argc; i++)
{
puts(argv[i]);
}
if(argc > 2)
{
return argc;
}
}
AFunction();
for(int i = 1; i <= 100; i++)
{
BFunction();
sum += i;
}
printf("sum = %d\n", sum);
return 0;
}