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