forked from OSchip/llvm-project
[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:
parent
40546cb381
commit
67f94e5a97
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
||||
%}
|
||||
|
|
|
@ -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;
|
||||
%}
|
||||
|
|
|
@ -4,3 +4,4 @@ _ZN12lldb_private*
|
|||
_ZNK12lldb_private*
|
||||
init_lld*
|
||||
PyInit__lldb*
|
||||
luaopen_lldb*
|
||||
|
|
|
@ -2,3 +2,4 @@ _ZN4lldb*
|
|||
_ZNK4lldb*
|
||||
init_lld*
|
||||
PyInit__lldb*
|
||||
luaopen_lldb*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
|
@ -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())
|
|
@ -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())
|
|
@ -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())
|
|
@ -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
|
||||
)
|
|
@ -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())
|
|
@ -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
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue