├── README └── debugger.lua /README: -------------------------------------------------------------------------------- 1 | A simply command line lua debugger. 2 | 3 | This debugger is commonly used in C/C++ project with lua, 4 | add debugger.lua file to the project's lua script directory, 5 | and load it. Then you can use debugger.addfuncbreak to add a 6 | function break point or debugger.addlinebreak to add line break point. 7 | This debugger uses command-line interacting with you, so your project 8 | should enable standard io. when breaking a point, you can do some 9 | operations, which is similar to gdb, and you can type 'h' for help. 10 | -------------------------------------------------------------------------------- /debugger.lua: -------------------------------------------------------------------------------- 1 | debugger = {} 2 | debugger.funcbreakpoints = {} 3 | debugger.linebreakpoints = {} 4 | debugger.luacode = {} 5 | debugger.command = {} 6 | debugger.idgenerator = 0 7 | 8 | debugger.generatebreakpointid = function() 9 | debugger.idgenerator = debugger.idgenerator + 1 10 | return debugger.idgenerator 11 | end 12 | 13 | debugger.addfuncbreak = function(fun) 14 | assert(type(fun) == "function", "must be a function") 15 | 16 | local id = debugger.generatebreakpointid() 17 | debugger.funcbreakpoints[fun] = id 18 | debugger.setnormalmode() 19 | 20 | print(string.format("id: %d, function break point added: ", id), fun) 21 | end 22 | 23 | debugger.delfuncbreak = function(fun) 24 | assert(type(fun) == "function", "must be a function") 25 | 26 | if debugger.funcbreakpoints[fun] then 27 | local id = debugger.funcbreakpoints[fun] 28 | debugger.funcbreakpoints[fun] = nil 29 | print(string.format("id: %d, function break point deleted", id), fun) 30 | else 31 | print("no break point:", fun) 32 | end 33 | end 34 | 35 | debugger.printfuncbreak = function() 36 | for fun, id in pairs(debugger.funcbreakpoints) do 37 | print(string.format("id: %d ", id), fun) 38 | end 39 | end 40 | 41 | debugger.addlinebreak = function(file, line) 42 | if not debugger.linebreakpoints[file] then 43 | debugger.linebreakpoints[file] = {} 44 | end 45 | 46 | local id = debugger.generatebreakpointid() 47 | debugger.linebreakpoints[file][line] = id 48 | debugger.setnormalmode() 49 | 50 | print(string.format("id: %d, line break point [%s]:%d added", id, string.sub(file, 2), line)) 51 | end 52 | 53 | debugger.dellinebreak = function(file, line) 54 | if debugger.linebreakpoints[file] and debugger.linebreakpoints[file][line] then 55 | local id = debugger.linebreakpoints[file][line] 56 | debugger.linebreakpoints[file][line] = nil 57 | print(string.format("id: %d, line break point [%s]:%d deleted", id, file, line)) 58 | return 59 | end 60 | 61 | print(string.format("no break point: [%s]:%d", file, line)) 62 | end 63 | 64 | debugger.printlinebreak = function() 65 | for file, lines in pairs(debugger.linebreakpoints) do 66 | for line, id in pairs(lines) do 67 | print(string.format("id: %d [%s]:%d", id, file, line)) 68 | end 69 | end 70 | end 71 | 72 | debugger.clearbreakpoint = function() 73 | debugger.funcbreakpoints = {} 74 | debugger.linebreakpoints = {} 75 | end 76 | 77 | debugger.loadluacode = function(filename) 78 | local file, errormsg = io.open(filename) 79 | if not file then 80 | print(errormsg) 81 | return 82 | end 83 | 84 | local code = {} 85 | for line in file:lines() do 86 | table.insert(code, line) 87 | end 88 | 89 | debugger.luacode[filename] = code 90 | file:close() 91 | end 92 | 93 | debugger.printluacode = function(file, line, ll) 94 | if not debugger.luacode[file] then 95 | debugger.loadluacode(file) 96 | end 97 | 98 | local code = debugger.luacode[file] 99 | if not code then 100 | return 101 | end 102 | 103 | local beginline = (line - ll) < 1 and 1 or (line - ll) 104 | local endline = (line + ll) > #code and #code or (line + ll) 105 | 106 | for i = beginline, endline do 107 | print(i, code[i]) 108 | end 109 | end 110 | 111 | debugger.setnormalmode = function() 112 | debug.sethook(debugger.normalmodehook, "cl") 113 | end 114 | 115 | debugger.setstepinmode = function() 116 | debug.sethook(debugger.stepinmodehook, "l") 117 | end 118 | 119 | debugger.setstepovermode = function() 120 | debug.sethook(debugger.stepovermodehook, "crl") 121 | end 122 | 123 | debugger.setnextlinemode = function() 124 | debug.sethook(debugger.nextlinemodehook, "crl") 125 | end 126 | 127 | debugger.checkbreakline = function(stacklevel, onbreak) 128 | local info = debug.getinfo(stacklevel, "Sl") 129 | for file, lines in pairs(debugger.linebreakpoints) do 130 | if file == info.source then 131 | for line, _ in pairs(lines) do 132 | if line == info.currentline then 133 | if onbreak then onbreak() end 134 | debugger.breakthepoint(stacklevel + 1) 135 | end 136 | end 137 | end 138 | end 139 | end 140 | 141 | debugger.checkbreakfunc = function(stacklevel, onbreak) 142 | local info = debug.getinfo(stacklevel, "f") 143 | for fun, _ in pairs(debugger.funcbreakpoints) do 144 | if fun == info.func then 145 | if onbreak then onbreak() end 146 | -- enter step in mode 147 | debugger.setstepinmode() 148 | end 149 | end 150 | end 151 | 152 | debugger.normalmodehook = function(event, line) 153 | if event == "line" then 154 | debugger.checkbreakline(3) 155 | elseif event == "call" then 156 | debugger.checkbreakfunc(3) 157 | end 158 | end 159 | 160 | debugger.stepinmodehook = function(event, line) 161 | debugger.breakthepoint(3) 162 | end 163 | 164 | debugger.stepovermodehook = function(event, line) 165 | local onbreak = function() 166 | debugger.stepoverbreak = nil 167 | debugger.callfunctimes = nil 168 | end 169 | 170 | if event == "line" then 171 | debugger.checkbreakline(3, onbreak) 172 | 173 | if debugger.stepoverbreak and debugger.callfunctimes == 0 then 174 | onbreak() 175 | debugger.breakthepoint(3) 176 | end 177 | elseif event == "call" then 178 | debugger.checkbreakfunc(3, onbreak) 179 | 180 | if debugger.stepoverbreak then 181 | debugger.callfunctimes = debugger.callfunctimes + 1 182 | end 183 | elseif event == "return" then 184 | if debugger.stepoverbreak then 185 | debugger.callfunctimes = debugger.callfunctimes - 1 186 | end 187 | end 188 | end 189 | 190 | debugger.nextlinemodehook = function(event, line) 191 | local onbreak = function() 192 | debugger.nextlinebreak = nil 193 | debugger.callfunctimes = nil 194 | end 195 | 196 | if event == "line" then 197 | debugger.checkbreakline(3, onbreak) 198 | 199 | if debugger.nextlinebreak and debugger.callfunctimes <= 1 then 200 | onbreak() 201 | debugger.breakthepoint(3) 202 | end 203 | elseif event == "call" then 204 | debugger.checkbreakfunc(3, onbreak) 205 | 206 | if debugger.nextlinebreak then 207 | debugger.callfunctimes = debugger.callfunctimes + 1 208 | end 209 | elseif event == "return" then 210 | if debugger.nextlinebreak then 211 | debugger.callfunctimes = debugger.callfunctimes - 1 212 | end 213 | end 214 | end 215 | 216 | debugger.breakthepoint = function(stacklevel, notprintcode) 217 | if not notprintcode then 218 | -- print currentline code 219 | local info = debug.getinfo(stacklevel, "Sl") 220 | debugger.printluacode(string.sub(info.source, 2), info.currentline, 0) 221 | end 222 | 223 | -- get command input 224 | print("debugger >") 225 | local l = io.read("*l") 226 | local command = debugger.parsecommand(l) 227 | debugger.execute(command, stacklevel + 1) 228 | end 229 | 230 | debugger.parsecommand = function(commandline) 231 | local t = {} 232 | for w in string.gmatch(commandline, "[^ ]+") do 233 | table.insert(t, w) 234 | end 235 | 236 | return t 237 | end 238 | 239 | debugger.execute = function(command, stacklevel) 240 | local executor = debugger.command[command[1]] 241 | if not executor then 242 | debugger.errorcmd() 243 | debugger.breakthepoint(stacklevel + 1) 244 | else 245 | executor(command, stacklevel + 1) 246 | end 247 | end 248 | 249 | debugger.breakline = function(command, stacklevel) 250 | local iscurrentfile = tonumber(command[2]) and true or false 251 | for i = 2, #command do 252 | local errorcmd = false 253 | if iscurrentfile and not tonumber(command[i]) then 254 | errorcmd = true 255 | end 256 | 257 | if not iscurrentfile and math.fmod(i, 2) == 1 and not tonumber(command[i]) then 258 | errorcmd = true 259 | end 260 | 261 | if errorcmd then 262 | return debugger.errorcmd() 263 | end 264 | end 265 | 266 | if iscurrentfile then 267 | local info = debug.getinfo(stacklevel, "S") 268 | for i = 2, #command do 269 | local line = tonumber(command[i]) 270 | debugger.addlinebreak(info.source, line) 271 | end 272 | else 273 | for i = 2, #command, 2 do 274 | local file = "@" .. command[i] 275 | local line = tonumber(command[i + 1]) 276 | debugger.addlinebreak(file, line) 277 | end 278 | end 279 | end 280 | 281 | debugger.breakfunc = function(command, stacklevel) 282 | for i = 2, #command do 283 | local chunk = loadstring(string.format("return %s", command[i])) 284 | local func = chunk and chunk() or nil 285 | 286 | if type(func) == "function" then 287 | debugger.addfuncbreak(func) 288 | print(string.format("break function: %s", command[i])) 289 | else 290 | print(string.format("no function %s, can't break it", command[i])) 291 | end 292 | end 293 | end 294 | 295 | debugger.breakpoint = function(command, stacklevel) 296 | if #command < 2 then 297 | debugger.errorcmd() 298 | return debugger.breakthepoint(stacklevel + 1) 299 | end 300 | 301 | local isbreakfunc = tonumber(command[2]) and 0 or 1 302 | isbreakfunc = (command[3] and tonumber(command[3])) and 0 or isbreakfunc 303 | 304 | if isbreakfunc == 1 then 305 | debugger.breakfunc(command, stacklevel + 1) 306 | else 307 | debugger.breakline(command, stacklevel + 1) 308 | end 309 | 310 | debugger.breakthepoint(stacklevel + 1, true) 311 | end 312 | 313 | debugger.continue = function(command, stacklevel) 314 | debugger.setnormalmode() 315 | end 316 | 317 | debugger.nextline = function(command, stacklevel) 318 | debugger.nextlinebreak = true 319 | debugger.callfunctimes = 1 320 | debugger.setnextlinemode() 321 | end 322 | 323 | debugger.stepin = function(command, stacklevel) 324 | debugger.setstepinmode() 325 | end 326 | 327 | debugger.stepover = function(command, stacklevel) 328 | debugger.stepoverbreak = true 329 | debugger.callfunctimes = 1 330 | debugger.setstepovermode() 331 | end 332 | 333 | debugger.printline = function(command, stacklevel) 334 | local info = debug.getinfo(stacklevel, "Sl") 335 | local lines = tonumber(command[2]) or 2 336 | debugger.printluacode(string.sub(info.source, 2), info.currentline, lines) 337 | 338 | debugger.breakthepoint(stacklevel + 1, true) 339 | end 340 | 341 | debugger.getlocalvaluetable = function(stacklevel) 342 | local j = 1 343 | local t = {} 344 | while true do 345 | local n, v = debug.getlocal(stacklevel, j) 346 | if not n then break end 347 | 348 | t[n] = v 349 | j = j + 1 350 | end 351 | 352 | return t 353 | end 354 | 355 | debugger.setlocalvaluetable = function(stacklevel, newlocals) 356 | local j = 1 357 | while true do 358 | local n, v = debug.getlocal(stacklevel, j) 359 | if not n then break end 360 | 361 | v = newlocals[n] 362 | assert(debug.setlocal(stacklevel, j, v) == n) 363 | j = j + 1 364 | end 365 | end 366 | 367 | debugger.getupvaluetable = function(stacklevel) 368 | local f = debug.getinfo(stacklevel, "f").func 369 | local j = 1 370 | local t = {} 371 | while true do 372 | local n, v = debug.getupvalue(f, j) 373 | if not n then break end 374 | 375 | t[n] = v 376 | j = j + 1 377 | end 378 | 379 | return t 380 | end 381 | 382 | debugger.setupvaluetable = function(stacklevel, newupvalues) 383 | local f = debug.getinfo(stacklevel, "f").func 384 | local j = 1 385 | while true do 386 | local n, v = debug.getupvalue(f, j) 387 | if not n then break end 388 | 389 | v = newupvalues[n] 390 | assert(debug.setupvalue(f, j, v) == n) 391 | j = j + 1 392 | end 393 | end 394 | 395 | debugger.getfuncenvtable = function(stacklevel) 396 | local fenv = getfenv(stacklevel) 397 | local upvaluetable = debugger.getupvaluetable(stacklevel + 1) 398 | setmetatable(upvaluetable, { __index = function(t, k) return fenv[k] end }) 399 | 400 | local localvaluetable = debugger.getlocalvaluetable(stacklevel + 1) 401 | setmetatable(localvaluetable, { __index = function(t, k) return upvaluetable[k] end, 402 | __newindex = function() assert(false) end }) 403 | return localvaluetable, upvaluetable, fenv 404 | end 405 | 406 | debugger.printvar = function(command, stacklevel) 407 | local getvalue = function(name, stacklevel) 408 | local chunk = loadstring(string.format("return %s", name)) 409 | if chunk then 410 | setfenv(chunk, debugger.getfuncenvtable(stacklevel + 1)) 411 | local value = { pcall(chunk) } 412 | if value[1] then 413 | table.remove(value, 1) 414 | if #value ~= 0 then 415 | return name, value 416 | end 417 | end 418 | end 419 | 420 | return name, nil 421 | end 422 | 423 | for i = 2, #command do 424 | local n, v = getvalue(command[i], stacklevel + 1) 425 | if type(v) == "table" then 426 | print(n, unpack(v)) 427 | else 428 | print(n, v) 429 | end 430 | end 431 | 432 | debugger.breakthepoint(stacklevel + 1, true) 433 | end 434 | 435 | debugger.traceback = function(command, stacklevel) 436 | local level = stacklevel 437 | while true do 438 | local info = debug.getinfo(level, "Sl") 439 | if not info then break end 440 | 441 | print(string.format("[%s]:%d", info.short_src, info.currentline)) 442 | level = level + 1 443 | end 444 | 445 | debugger.breakthepoint(stacklevel + 1, true) 446 | end 447 | 448 | debugger.printbreakpoint = function(command, stacklevel) 449 | debugger.printfuncbreak() 450 | debugger.printlinebreak() 451 | debugger.breakthepoint(stacklevel + 1, true) 452 | end 453 | 454 | debugger.clearbreak = function(command, stacklevel) 455 | debugger.clearbreakpoint() 456 | print("clear all break points ok") 457 | debugger.breakthepoint(stacklevel + 1, true) 458 | end 459 | 460 | debugger.deletebreak = function(command, stacklevel) 461 | local getfuncbreakpoint = function(breakpointid) 462 | for func, id in pairs(debugger.funcbreakpoints) do 463 | if id == breakpointid then 464 | return func 465 | end 466 | end 467 | end 468 | 469 | local getlinebreakpoint = function(breakpointid) 470 | for file, lines in pairs(debugger.linebreakpoints) do 471 | for line, id in pairs(lines) do 472 | if id == breakpointid then 473 | return file, line 474 | end 475 | end 476 | end 477 | end 478 | 479 | for i = 2, #command do 480 | local id = tonumber(command[i]) 481 | if id then 482 | local func = getfuncbreakpoint(id) 483 | if func then 484 | debugger.delfuncbreak(func) 485 | else 486 | local file, line = getlinebreakpoint(id) 487 | if file and line then 488 | debugger.dellinebreak(file, line) 489 | end 490 | end 491 | end 492 | end 493 | 494 | debugger.breakthepoint(stacklevel + 1, true) 495 | end 496 | 497 | debugger.dosetvalue = function(command, stacklevel) 498 | local setcommand = string.format("%s = %s", command[2], command[3]) 499 | local chunk = loadstring(setcommand) 500 | 501 | if chunk then 502 | local localvaluetable, upvaluetable = debugger.getfuncenvtable(stacklevel + 1) 503 | setfenv(chunk, localvaluetable) 504 | if pcall(chunk) then 505 | debugger.setlocalvaluetable(stacklevel + 1, localvaluetable) 506 | debugger.setupvaluetable(stacklevel + 1, upvaluetable) 507 | return setcommand .. " ok" 508 | end 509 | end 510 | 511 | return setcommand .. " failed" 512 | end 513 | 514 | debugger.setvalue = function(command, stacklevel) 515 | if #command < 3 then 516 | debugger.errorcmd() 517 | return debugger.breakthepoint(stacklevel + 1) 518 | end 519 | 520 | local msg = debugger.dosetvalue(command, stacklevel + 1) 521 | print(msg) 522 | debugger.breakthepoint(stacklevel + 1, true) 523 | end 524 | 525 | debugger.help = function(command, stacklevel) 526 | local str = [[ 527 | b -- break line. eg. b file line ... 528 | current file: eg. b line line ... 529 | function: eg. b func func ... 530 | c -- continue 531 | n -- next line 532 | s -- step in 533 | o -- step over 534 | l -- print context lua code. eg. l [lines] 535 | p -- print var value. eg. p var1 var2 ... 536 | t -- traceback 537 | pb -- print all break points 538 | cb -- clear all break points 539 | db -- delete break point. eg. db id1 id2 ... 540 | set -- set value. eg. set var value 541 | h -- for help 542 | ]] 543 | 544 | print(str) 545 | debugger.breakthepoint(stacklevel + 1, true) 546 | end 547 | 548 | debugger.errorcmd = function() 549 | print("error command, h for help") 550 | end 551 | 552 | debugger.command["b"] = debugger.breakpoint 553 | debugger.command["c"] = debugger.continue 554 | debugger.command["n"] = debugger.nextline 555 | debugger.command["s"] = debugger.stepin 556 | debugger.command["o"] = debugger.stepover 557 | debugger.command["l"] = debugger.printline 558 | debugger.command["p"] = debugger.printvar 559 | debugger.command["t"] = debugger.traceback 560 | debugger.command["pb"] = debugger.printbreakpoint 561 | debugger.command["cb"] = debugger.clearbreak 562 | debugger.command["db"] = debugger.deletebreak 563 | debugger.command["set"] = debugger.setvalue 564 | debugger.command["h"] = debugger.help 565 | --------------------------------------------------------------------------------