├── LICENSE ├── Makefile ├── ldebug.lua ├── lsocket.c └── test.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 codingnow.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all : 2 | @echo Please do 'make PLATFORM' where PLATFORM is one of these : linux mingw 3 | 4 | CFLAGS := -g -Wall 5 | LDFLAGS := --shared 6 | 7 | mingw : TARGET := ldebug.dll 8 | mingw : CFLAGS += -I/usr/local/include 9 | mingw : LDFLAGS += -L/usr/local/bin -llua52 -lws2_32 10 | 11 | mingw : ldebug 12 | 13 | linux : TARGET := ldebug.so 14 | linux : CFLAGS += -I/usr/local/include 15 | linux : LDFLAGS += -fPIC 16 | 17 | linux : ldebug 18 | 19 | 20 | ldebug : lsocket.c 21 | gcc -o $(TARGET) $(CFLAGS) $^ $(LDFLAGS) 22 | 23 | clean : 24 | rm -f ldebug.dll ldebug.so -------------------------------------------------------------------------------- /ldebug.lua: -------------------------------------------------------------------------------- 1 | local csock = require "ldebug.socket" 2 | local tconcat = table.concat 3 | local tinsert = table.insert 4 | local srep = string.rep 5 | local type = type 6 | local pairs = pairs 7 | local tostring = tostring 8 | local next = next 9 | 10 | local ldebug = {} 11 | 12 | local socks_fd = nil 13 | local socks_buffer = "" 14 | local socks_prompt = "debug>" 15 | local debug_env = {} 16 | local debug_cmd = {} 17 | local im_cmd = {} 18 | local stack_frame_base 19 | local enter_im = false 20 | local in_hook = false 21 | 22 | setmetatable(debug_env , { __index = _ENV }) 23 | 24 | local function short_value(root) 25 | if type(root) ~= "table" then 26 | if type(root) == "string" then 27 | return string.format("'%s'", root) 28 | end 29 | return tostring(root) 30 | end 31 | local cache = { [root] = "." } 32 | local function _dump(t, name, depth) 33 | local temp = {} 34 | for k, v in ipairs(t) do 35 | if type(v) == "table" then 36 | local new_key = string.format("[%d].%s", k,key) 37 | tinsert(temp,_dump(v,new_key)) 38 | else 39 | tinsert(temp,tostring(v)) 40 | end 41 | end 42 | for k,v in pairs(t) do 43 | local key 44 | if type(k) ~= "number" then 45 | key = tostring(k) 46 | k = 0 47 | else 48 | key = "[" .. k .. "]" 49 | end 50 | if not(k>0 and k<=#t) then 51 | if cache[v] then 52 | tinsert(temp,string.format("%s = @%s", key, cache[v])) 53 | elseif type(v) == "table" and depth < 3 then 54 | local new_key = name .. "." .. key 55 | cache[v] = new_key 56 | tinsert(temp,string.format("%s = { %s }", key, _dump(v,new_key, depth+1))) 57 | else 58 | if type(v) == "string" then 59 | v = string.format("'%s'",v) 60 | end 61 | tinsert(temp, string.format("%s = %s", key , tostring(v))) 62 | end 63 | end 64 | end 65 | return tconcat(temp,", ") 66 | end 67 | local v = _dump(root, "", 0) 68 | if #v > 512 then 69 | return string.format("{ %s ...}", string.sub(v,1,256)) 70 | else 71 | return string.format("{ %s }", v) 72 | end 73 | end 74 | 75 | local function tostring_r(root) 76 | if type(root) ~= "table" then 77 | return tostring(root) 78 | end 79 | local cache = { [root] = "." } 80 | local function _dump(t,space,name) 81 | local temp = {} 82 | for k,v in pairs(t) do 83 | local key = tostring(k) 84 | if cache[v] then 85 | tinsert(temp,"+" .. key .. " {" .. cache[v].."}") 86 | elseif type(v) == "table" then 87 | local new_key = name .. "." .. key 88 | cache[v] = new_key 89 | tinsert(temp,"+" .. key .. _dump(v,space .. (next(t,k) and "|" or " " ).. srep(" ",#key),new_key)) 90 | else 91 | tinsert(temp,"+" .. key .. " [" .. tostring(v).."]") 92 | end 93 | end 94 | return tconcat(temp,"\n"..space) 95 | end 96 | return _dump(root, "","") 97 | end 98 | 99 | local function reply(r) 100 | csock.write(socks_fd, tostring_r(r) .. "\n") 101 | csock.write(socks_fd, socks_prompt) 102 | end 103 | 104 | function debug_env.print(...) 105 | for _,v in ipairs {...} do 106 | csock.write(socks_fd, tostring_r(v) .. "\t") 107 | end 108 | end 109 | 110 | function debug_cmd.help() 111 | reply[[ 112 | You can enter any valid lua code or enter command below: 113 | help : show this help messages 114 | stop : stop the program at one of probe and then enter interactive mode.]] 115 | end 116 | debug_cmd["?"] = debug_cmd.help 117 | 118 | function debug_cmd.stop() 119 | enter_im = true 120 | end 121 | 122 | function ldebug.start(host) 123 | local ip = (host and host.ip) or "127.0.0.1" 124 | local port = (host and host.port) or 6789 125 | socks_fd = csock.start(ip , port) 126 | end 127 | 128 | local function split() 129 | local b,e = string.find(socks_buffer, "\n", 1, true) 130 | if b then 131 | local b = string.find(socks_buffer, "\r", -b, true) or b 132 | local ret = string.sub(socks_buffer,1,b-1) 133 | socks_buffer = string.sub(socks_buffer, e+1) 134 | return ret 135 | end 136 | end 137 | 138 | function readline() 139 | local ret = split() 140 | if ret then 141 | return ret 142 | end 143 | local data = csock.read(socks_fd, socks_prompt) 144 | if data then 145 | socks_buffer = socks_buffer .. data 146 | return split() 147 | end 148 | 149 | return data 150 | end 151 | 152 | local function do_cmd(cmd) 153 | if debug_cmd[cmd] then 154 | debug_cmd[cmd]() 155 | elseif string.byte(cmd) == 61 then 156 | -- 61 means '=' 157 | local f,err = load("return " .. string.sub(cmd,2), "=console", "t", debug_env) 158 | if not f then 159 | reply(err) 160 | else 161 | local _, r = pcall(f) 162 | reply(r) 163 | end 164 | else 165 | local f,err = load(cmd, "=console", "t", debug_env) 166 | if not f then 167 | reply(err) 168 | else 169 | local ok , r = pcall(f) 170 | if ok then 171 | csock.write(socks_fd, socks_prompt) 172 | else 173 | reply(r) 174 | end 175 | end 176 | end 177 | end 178 | 179 | function im_cmd.h() 180 | reply [[ 181 | s : run step 182 | n : run next 183 | r : run until return 184 | l (level) : show locals at level 185 | p var : print var 186 | c : continue to next probe 187 | h : help message 188 | f : show stack frame 189 | q : quit interactive mode]] 190 | end 191 | im_cmd["?"] = im_cmd.h 192 | 193 | function im_cmd.f() 194 | local index = 1 195 | local tmp = {} 196 | while true do 197 | local info = debug.getinfo(stack_frame_base + index, "Sl") 198 | if info == nil then 199 | break 200 | end 201 | local source = string.format("[%d] %s:%d",index, info.short_src,info.currentline) 202 | tinsert(tmp, source) 203 | index = index + 1 204 | end 205 | reply(table.concat(tmp, "\n")) 206 | end 207 | 208 | function im_cmd.q() 209 | return true 210 | end 211 | 212 | function im_cmd.c() 213 | enter_im = true 214 | return true 215 | end 216 | 217 | function im_cmd.l(cmd) 218 | local level = tonumber(cmd[2]) or 1 219 | local s = stack_frame_base + level 220 | local info = debug.getinfo(s, "uf") 221 | local tmp = {} 222 | for i = 1, info.nparams do 223 | local name , value = debug.getlocal(s,i) 224 | tinsert(tmp, string.format("P %s : %s", name, short_value(value))) 225 | end 226 | if info.isvararg then 227 | local index = -1 228 | while true do 229 | local name ,value = debug.getlocal(s,index) 230 | if name == nil then 231 | break 232 | end 233 | tinsert(tmp, string.format("P [%d] : %s", -index, short_value(value))) 234 | index = index - 1 235 | end 236 | end 237 | local index = info.nparams + 1 238 | while true do 239 | local name ,value = debug.getlocal(s,index) 240 | if name == nil then 241 | break 242 | end 243 | tinsert(tmp, string.format("L %s : %s", name, short_value(value))) 244 | index = index + 1 245 | end 246 | for i = 1, info.nups do 247 | local name , value = debug.getupvalue(info.func,i) 248 | tinsert(tmp, string.format("U %s : %s", name, short_value(value))) 249 | end 250 | 251 | reply(table.concat(tmp, "\n")) 252 | end 253 | 254 | local function find_local(s, key) 255 | local index = 1 256 | while true do 257 | local name ,value = debug.getlocal(s,index) 258 | if name == nil then 259 | break 260 | end 261 | if name == key then 262 | return value 263 | end 264 | index = index + 1 265 | end 266 | end 267 | 268 | local function find_upvalue(s, key) 269 | local info = debug.getinfo(s, "f") 270 | local index = 1 271 | while true do 272 | local name ,value = debug.getupvalue(info.func,index) 273 | if name == nil then 274 | break 275 | end 276 | if name == key then 277 | return value 278 | end 279 | index = index + 1 280 | end 281 | end 282 | 283 | function im_cmd.p(cmd) 284 | local name = cmd[2] 285 | local level = tonumber(cmd[3]) or 1 286 | if name == nil then 287 | reply("need a var name") 288 | return 289 | end 290 | local base = stack_frame_base + level - 1 291 | if name == "..." then 292 | local tmp = {} 293 | local index = -1 294 | local s = base + 1 295 | while true do 296 | local name ,value = debug.getlocal(s,index) 297 | if name == nil then 298 | break 299 | end 300 | tinsert(tmp, string.format("[%d] : %s", -index, tostring_r(value))) 301 | index = index - 1 302 | end 303 | reply(table.concat(tmp,"\n")) 304 | return 305 | end 306 | local v = find_local(base + 2, name) 307 | if v then 308 | reply(tostring_r(v)) 309 | return 310 | end 311 | v = find_upvalue(base + 2, name) 312 | if v then 313 | reply(tostring_r(v)) 314 | return 315 | end 316 | reply(tostring_r(_ENV[name])) 317 | end 318 | 319 | local function dispatch_im() 320 | local cmd = {} 321 | while true do 322 | local line = readline() 323 | if line then 324 | local i = 1 325 | for v in string.gmatch(line, "[^ \t]+") do 326 | cmd[i] = v 327 | i = i + 1 328 | end 329 | for j = i, #cmd do 330 | cmd[j] = nil 331 | end 332 | local c = cmd[1] 333 | if c == nil then 334 | csock.write(socks_fd, socks_prompt) 335 | elseif im_cmd[c] == nil then 336 | reply("Invalid command, type ? for help") 337 | else 338 | local ok ,err = pcall(im_cmd[c], cmd) 339 | if not ok then 340 | reply("Invalid command : " .. err) 341 | else 342 | if err then 343 | break 344 | end 345 | end 346 | end 347 | else 348 | csock.yield() 349 | end 350 | end 351 | end 352 | 353 | function ldebug.probe() 354 | if enter_im == true then 355 | debug.sethook() 356 | enter_im = false 357 | local info = debug.getinfo(2, "Sl") 358 | stack_frame_base = 4 359 | socks_prompt = string.format("%s:%d>",info.short_src,info.currentline) 360 | csock.write(socks_fd, socks_prompt) 361 | in_hook = false 362 | dispatch_im() 363 | if not enter_im then 364 | socks_prompt = "debug>" 365 | reply "The program continue running" 366 | end 367 | else 368 | local line = readline() 369 | if line then 370 | do_cmd(line) 371 | end 372 | end 373 | end 374 | 375 | local stack_depth 376 | local function _hook(mode) 377 | if stack_depth then 378 | if mode == "call" or mode == "tail call" then 379 | stack_depth = stack_depth + 1 380 | elseif mode == "return" then 381 | stack_depth = stack_depth - 1 382 | end 383 | if stack_depth > 0 then 384 | return 385 | end 386 | stack_depth = nil 387 | end 388 | local info = debug.getinfo(2, "Sl") 389 | stack_frame_base = 4 390 | socks_prompt = string.format("%s:%d>",info.short_src,info.currentline) 391 | csock.write(socks_fd, socks_prompt) 392 | in_hook = true 393 | dispatch_im() 394 | if not enter_im then 395 | debug.sethook() 396 | enter_im = false 397 | socks_prompt = "debug>" 398 | reply "The program continue running" 399 | end 400 | end 401 | 402 | local function debug_hook_step(level) 403 | debug.sethook(function(mode) 404 | if mode == "call" then 405 | level = level+1 406 | elseif mode == "return" then 407 | level = level-1 408 | end 409 | if level == 0 then 410 | debug.sethook(_hook, "l") 411 | end 412 | end, "cr") 413 | end 414 | 415 | function im_cmd.s() 416 | enter_im = "hook" 417 | if in_hook then 418 | debug.sethook(_hook,"l") 419 | else 420 | debug_hook_step(6) 421 | end 422 | return true 423 | end 424 | 425 | local function debug_hook_next(level) 426 | debug.sethook(function(mode) 427 | if mode == "call" then 428 | level = level+1 429 | elseif mode == "return" then 430 | level = level-1 431 | end 432 | if level == 0 then 433 | stack_depth = 0 434 | debug.sethook(_hook, "lcr") 435 | end 436 | end, "cr") 437 | end 438 | 439 | function im_cmd.n() 440 | enter_im = "hook" 441 | if in_hook then 442 | debug_hook_next(0) 443 | else 444 | debug_hook_next(6) 445 | end 446 | return true 447 | end 448 | 449 | function im_cmd.r() 450 | enter_im = "hook" 451 | if in_hook then 452 | debug_hook_step(1) 453 | else 454 | debug_hook_step(7) 455 | end 456 | return true 457 | end 458 | 459 | return ldebug 460 | -------------------------------------------------------------------------------- /lsocket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef WIN32 9 | # include 10 | # include 11 | 12 | static void 13 | startup() { 14 | WORD wVersionRequested; 15 | WSADATA wsaData; 16 | int err; 17 | 18 | wVersionRequested = MAKEWORD(2, 2); 19 | 20 | err = WSAStartup(wVersionRequested, &wsaData); 21 | if (err != 0) { 22 | printf("WSAStartup failed with error: %d\n", err); 23 | exit(1); 24 | } 25 | } 26 | 27 | static void 28 | yield() { 29 | Sleep(0); 30 | } 31 | 32 | #define EINTR WSAEINTR 33 | #define EWOULDBLOCK WSAEWOULDBLOCK 34 | 35 | #else 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #define closesocket close 46 | 47 | static void 48 | startup() { 49 | } 50 | 51 | static void 52 | yield() { 53 | sleep(0); 54 | } 55 | 56 | #endif 57 | 58 | #define BUFFER_SIZE 1024 59 | 60 | struct socket { 61 | int listen_fd; 62 | int fd; 63 | int closed; 64 | }; 65 | 66 | static int 67 | lstart(lua_State *L) { 68 | const char * addr = luaL_checkstring(L,1); 69 | int port = luaL_checkinteger(L,2); 70 | 71 | struct socket * s = lua_newuserdata(L, sizeof(*s)); 72 | s->listen_fd = -1; 73 | s->fd = -1; 74 | s->closed = 0; 75 | 76 | int lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 77 | int reuse = 1; 78 | setsockopt(s->listen_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(int)); 79 | 80 | struct sockaddr_in service; 81 | 82 | service.sin_family = AF_INET; 83 | service.sin_addr.s_addr = inet_addr(addr); 84 | service.sin_port = htons(port); 85 | 86 | if (bind(lfd, (const struct sockaddr *)&service, sizeof(service)) < 0) { 87 | closesocket(lfd); 88 | printf("bind() failed"); 89 | exit(1); 90 | } 91 | if (listen(lfd, 1) < 0) { 92 | printf("listen(): Error"); 93 | exit(1); 94 | } 95 | s->listen_fd = lfd; 96 | 97 | return 1; 98 | } 99 | 100 | static int 101 | fdcanread(int fd) { 102 | fd_set rfds; 103 | struct timeval tv = {0,0}; 104 | 105 | FD_ZERO(&rfds); 106 | FD_SET(fd, &rfds); 107 | 108 | return select(fd+1, &rfds, NULL, NULL, &tv) == 1; 109 | } 110 | 111 | static int 112 | test(struct socket *s, const char * welcome, size_t sz) { 113 | if (s->closed) { 114 | closesocket(s->fd); 115 | s->fd = -1; 116 | s->closed = 0; 117 | } 118 | if (s->fd < 0) { 119 | if (fdcanread(s->listen_fd)) { 120 | s->fd = accept(s->listen_fd, NULL, NULL); 121 | if (s->fd < 0) { 122 | return -1; 123 | } 124 | send(s->fd , welcome , sz , 0 ); 125 | } 126 | } 127 | if (fdcanread(s->fd)) { 128 | return s->fd; 129 | } 130 | return -1; 131 | 132 | } 133 | 134 | static int 135 | lread(lua_State *L) { 136 | struct socket * s = lua_touserdata(L,1); 137 | if (s == NULL || s->listen_fd < 0) { 138 | return luaL_error(L, "start socket first"); 139 | } 140 | size_t sz = 0; 141 | const char * welcome = luaL_checklstring(L,2,&sz); 142 | int fd = test(s, welcome,sz); 143 | if (fd >= 0) { 144 | char buffer[BUFFER_SIZE]; 145 | int rd = recv(fd, buffer, BUFFER_SIZE, 0); 146 | if (rd <= 0) { 147 | s->closed = 1; 148 | lua_pushboolean(L, 0); 149 | return 1; 150 | } 151 | lua_pushlstring(L, buffer, rd); 152 | return 1; 153 | } 154 | return 0; 155 | } 156 | 157 | static int 158 | lwrite(lua_State *L) { 159 | struct socket * s = lua_touserdata(L,1); 160 | if (s == NULL || s->listen_fd < 0 || s->fd < 0) { 161 | return luaL_error(L, "start socket first"); 162 | } 163 | size_t sz = 0; 164 | const char * buffer = luaL_checklstring(L, 2, &sz); 165 | int p = 0; 166 | for (;;) { 167 | int wt = send(s->fd, buffer+p, sz-p, 0); 168 | if (wt < 0) { 169 | switch(errno) { 170 | case EWOULDBLOCK: 171 | case EINTR: 172 | continue; 173 | default: 174 | closesocket(s->fd); 175 | s->fd = -1; 176 | lua_pushboolean(L,0); 177 | return 1; 178 | } 179 | } 180 | if (wt == sz - p) 181 | break; 182 | p+=wt; 183 | } 184 | if (s->closed) { 185 | closesocket(s->fd); 186 | s->fd = -1; 187 | s->closed = 0; 188 | } 189 | 190 | return 0; 191 | } 192 | 193 | static int 194 | lyield(lua_State *L) { 195 | yield(); 196 | return 0; 197 | } 198 | 199 | int 200 | luaopen_ldebug_socket(lua_State *L) { 201 | luaL_checkversion(L); 202 | startup(); 203 | luaL_Reg l[] = { 204 | { "start", lstart }, 205 | { "read", lread }, 206 | { "write", lwrite }, 207 | { "yield", lyield }, 208 | { NULL, NULL }, 209 | }; 210 | 211 | luaL_newlib(L,l); 212 | 213 | return 1; 214 | } 215 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | local ldebug = require "ldebug" 2 | 3 | ldebug.start { ip = "127.0.0.1", port = 6789 } 4 | print("telnet 127.0.0.1 6789 to login") 5 | 6 | local a = {1,2,3,p={x=1,y=2}} 7 | local b = 1 8 | 9 | local function test() 10 | b = b + 1 11 | end 12 | 13 | function f(a,...) 14 | local i = 0 15 | while true do 16 | ldebug.probe() 17 | i = i + 1 18 | ldebug.probe() 19 | test() 20 | end 21 | end 22 | 23 | f(1,2,3) 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------