I thought about two quick solutions:
- redirect standard streams;
- redefine printf and it's adjacent functions.
Since standard streams, both stdout and stderr, in my case were already redefined (for remote debugging purposes), I didn't had much to choose from.
Looking into Lua 5.1 sources, I've found that stdout was simply hard-coded in some places. During up-streaming this patch to Lua 5.2 surprisingly most of the hard-coded stream names went away, and it looks like it would be possible to use redefined print without losing anything. I haven't tested this though. Following just works for Lua 5.1.x and above.
Patching Lua
Original patch was implemented for Lua 5.1, but yesterday at I was asked to upstream it to 5.2 too.
Patches are available here: 5.1.5 and 5.2.1.
Step into Lua source top folder, apply the patch:
Lua 5.1.5:
patch -p1 < ~/download/lua-5.1.5-output-redirect.patchLua 5.2.1:
patch -p1 < ~/download/lua-5.2.1-output-redirect.patch
Building patched Lua library
I'm too lazy to hack the Makefile and provide a clean solution, but building is straight-forward anyway. Check the Makefile in src/ folder for make parameters for your platform, and pass LUA_REDIRECT define from the command line.Lua 5.1.5 for Linux:
cd src make a LUA_A="liblua-5.1.5-redirect.a" MYCFLAGS="-DLUA_USE_LINUX -DLUA_REDIRECT" MYLIBS="-Wl,-E -ldl -lreadline -lhistory -lncurses"Lua 5.2.1 for Linux:
cd src make a LUA_A="liblua-5.2.1-redirect.a" SYSCFLAGS="-DLUA_USE_LINUX -DLUA_REDIRECT" SYSLIBS="-Wl,-E -ldl -lreadline -lncurses"Some clarifications:
- Building `luac` or `lua` binaries will lead to unresolved externals. `make a` will build only the library, which is what we want.
- LUA_A="liblua-5.x.y-redirect.a" tells the Makefile to save our library to a non-default (`liblua.a`) name. This is in case if you want to build `lua` or `luac` binaries from the same sources. Sure, you can choose whatever name you want, but I prefer to have unique names and there is a hidden reason for that, which is out of scope of this topic.
Checking assembled Lua library
Check if hooks are present, and Lua does not link to `printf` and other redefined functions:nm liblua-5.x.y-redirect.a | grep printf nm liblua-5.x.y-redirect.a | grep fputs nm liblua-5.x.y-redirect.a | grep fwriteAll listed symbols should be prefixed with lua_, i.e.:
#> nm liblua-redirect.a | grep printf 0000000000000008 C lua_fprintf 0000000000000008 C lua_printf 0000000000000008 C lua_fprintf 0000000000000008 C lua_printf ...
Testing Lua redirect
To nicely test Lua output redirect, it is required to re-implement standard output functions. Sure, the easiest way would be just to hook Lua back to standard functions:lua_fprintf = fprintf; lua_fputs = fputs; lua_fwrite = fwrite; lua_printf = printf;But we want our output into the buffer! The resulting test program is below (download here):
// NB! luaredir.h and other Lua includes should go *before*
// standard library headers, otherwise luaredir.h will
// redefine printf/fprintf/fputs/fwrite functions, and
// they will become unusable in a given file.
extern "C" {
#include "luaredir.h"
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
// following includes should be placed in brackets rather than quotes,
// intentially replaced due to blog highlighter issues
#include "cstdio"
#include "cstring"
#include "iostream"
#include "string"
using namespace std;
// lua standard stream buffers
string stdout_buf;
string stderr_buf;
// fprintf hook for Lua output redirection
int redirect_fprintf(FILE* stream, const char* msg, ...) {
char buffer[1024];
va_list args;
va_start(args, msg);
vsprintf(buffer, msg, args);
va_end(args);
if (stream == stdout)
stdout_buf.append(buffer);
else
stderr_buf.append(buffer);
return strlen(buffer);
}
// fputs hook for Lua output redirection
int redirect_fputs(const char* msg, FILE* stream) {
return redirect_fprintf(stream, msg);
}
// fwrite hook for Lua output redirection
int redirect_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
memcpy(buffer, ptr, size * count);
if (stream == stdout)
stdout_buf.append(buffer);
else
stderr_buf.append(buffer);
return count;
}
// printf hook for Lua output redirection
int redirect_printf(const char* msg, ...) {
char buffer[1024];
va_list args;
va_start(args, msg);
vsprintf(buffer, msg, args);
va_end(args);
stdout_buf.append(buffer);
return strlen(buffer);
}
// actual code
int main() {
int lua_error;
// connect redirection hooks
lua_fprintf = redirect_fprintf;
lua_fputs = redirect_fputs;
lua_fwrite = redirect_fwrite;
lua_printf = redirect_printf;
// initialize Lua
lua_State *luaVM = luaL_newstate();
luaL_openlibs(luaVM);
if (luaVM == NULL) {
printf("Error initializing lua!\n");
return -1;
}
// execute Lua test code, and print it's precious output
lua_error = luaL_dostring(luaVM, "print(\"hello world!\")");
printf("===== Test 1 output =====\n");
printf("Lua stdout buffer:\n---\n%s\n---\n", stdout_buf.c_str());
printf("Lua stderr buffer:\n---\n%s\n---\n", stderr_buf.c_str());
printf("Lua error message:\n---\n%s\n---\n", lua_tostring(luaVM, -1));
stdout_buf.clear();
stderr_buf.clear();
// execute Lua test code, and print it's precious output
lua_error = luaL_dostring(luaVM, "bad_function()");
printf("===== Test 2 output =====\n");
printf("Lua stdout buffer:\n---\n%s\n---\n", stdout_buf.c_str());
printf("Lua stderr buffer:\n---\n%s\n---\n", stderr_buf.c_str());
printf("Lua error message:\n---\n%s\n---\n", lua_tostring(luaVM, -1));
stdout_buf.clear();
stderr_buf.clear();
// cleanup and quit
lua_close(luaVM);
return 0;
}This test program was built using following command line:
g++ -DLUA_REDIRECT -I/path/to/lua-5.x.y/src/ -L. lua-redirect-test.cpp -llua-5.x.y-redirect -ldl -o lua-redirect-testTest program output:
===== Test 1 output ===== Lua stdout buffer: --- hello world! --- Lua stderr buffer: --- --- Lua error message: --- (null) --- ===== Test 2 output ===== Lua stdout buffer: --- --- Lua stderr buffer: --- --- Lua error message: --- [string "bad_function()"]:1: attempt to call global 'bad_function' (a nil value) ---
No comments:
Post a Comment