Giter VIP home page Giter VIP logo

debugger.lua's Introduction

🐞 debugger.lua πŸŒ–

A simple, embedabble debugger for Lua 5.x, and LuaJIT 2.x.

ExampleLog

debugger.lua is a simple, single file, pure Lua debugger that is easy to integrate with any project. The lua-users wiki lists a number of debuggers. clidebugger was closest to what I was looking for, but I ran into several compatibility issues, and the rest are pretty big libraries with a whole lot of dependencies. I just wanted something simple to integrate that would work through stdin/stdout. I also decided that it sounded fun to try and make my own!

βœ… Features

  • Trivial to "install". Can be integrated as a single .lua or .h file.
  • The regular assortment of commands you'd expect from a debugger: continue, step, next, finish, print/eval expression, move up/down the stack, backtrace, print locals, inline help.
  • Evaluate expressions, call functions interactively, and get/set variables.
  • Pretty printed output so you see {1 = 3, "a" = 5} instead of table: 0x10010cfa0
  • Speed! The debugger hooks are only set during the step/next/finish commands.
  • Conditional, assert-style breakpoints.
  • Colored output and line editing support when possible.
  • Drop in replacements for Lua's assert(), error(), and pcall() functions that trigger the debugger.
  • When using the C API, dbg_call() works as a drop-in replacement for lua_pcall().
  • IO can easily be remapped to a socket or window by overwriting the dbg.write() and dbg.read() functions.
  • Permissive MIT license.

πŸ™Œ Easy to use from C too!

debugger.lua can be easily integrated into an embedded project with just a .h file. First though, you'll need to run lua make_c_header.lua. This generates debugger_lua.h by inserting the lua code into a template .h file.

// You need to define this in one of your C files. (and only one!)
#define DEBUGGER_LUA_IMPLEMENTATION
#include "debugger_lua.h"

int main(int argc, char **argv){
	// Do normal Lua init stuff.
	lua_State *lua = luaL_newstate();
	luaL_openlibs(lua);
	
	// Load up the debugger module as "debugger".
	// Also stores it in a global variable "dbg".
	// Use dbg_setup() to change these or use custom I/O.
	dbg_setup_default(lua);
	
	// Load some buggy Lua code.
	luaL_loadstring(lua,
		"local num = 1 \n"
		"local str = 'one' \n"
		"local res = num + str \n"
	);
	
	// Run it in the debugger. This function works just like lua_pcall() otherwise.
	// Note that setting your own custom message handler disables the debugger.
	if(dbg_pcall(lua, 0, 0, 0)){
		fprintf(stderr, "Lua Error: %s\n", lua_tostring(lua, -1));
	}
}

Now in your Lua code you can access dbg or use require 'debugger'.

🐝 Debugger Commands:

If you have used other CLI debuggers, debugger.lua shouldn't be surprising. I didn't make a fancy parser, so the commands are just single letters. Since the debugger is pretty simple there are only a small handful of commands anwyay.

[return] - re-run last command
c(ontinue) - contiue execution
s(tep) - step forward by one line (into functions)
n(ext) - step forward by one line (skipping over functions)
p(rint) [expression] - execute the expression and print the result
f(inish) - step forward until exiting the current function
u(p) - move up the stack by one frame
d(own) - move down the stack by one frame
w(here) [line count] - print source code around the current line
t(race) - print the stack trace
l(ocals) - print the function arguments, locals and upvalues.
h(elp) - print this message
q(uit) - halt execution

If you've never used a command line debugger before, start a nice warm cozy fire, run tutorial.lua, and open it up in your favorite editor so you can follow along.

πŸ¦‹ Debugger API

There are several overloadable functions you can use to customize debugger.lua.

  • dbg.read(prompt) - Show the prompt and block for user input. (Defaults to read from stdin)
  • dbg.write(str) - Write a string to the output. (Defaults to write to stdout)
  • dbg.shorten_path(path) - Return a shortened version of a path. (Defaults to simply return path)
  • dbg.exit(err) - Stop debugging. (Defaults to os.exit(err))
  • dbg.pretty(obj) - Output a pretty print string for an object. (Defaults to a reasonable version using __tostring metamethods and such)

Using these you can customize the debugger to work in your environment. For instance, you can divert the I/O over a network socket or to a GUI window.

There are also some goodies you can use to make debugging easier.

  • dbg.writeln(format, ...) - Basically the same as dbg.write(string.format(format.."\n", ...))
  • dbg.pretty_depth = int - Set how deep dbg.pretty() formats tables.
  • dbg.pretty(obj) - Will return a pretty print string of an object.
  • dbg.pp(obj) - Basically the same as dbg.writeln(dbg.pretty(obj))
  • dbg.auto_where = int_or_false - Set the where command to run automatically when the active line changes. The value is the number of context lines.
  • dbg.error(error, [level]) - Drop in replacement for error() that breaks in the debugger.
  • dbg.assert(error, [message]) - Drop in replacement for assert() that breaks in the debugger.
  • dbg.call(f, ...) - Drop in replacement for pcall() that breaks in the debugger.

πŸͺ² Environment Variables:

Want to disable ANSI color support or disable GNU readline? Set the DBG_NOCOLOR and/or DBG_NOREADLINE environment variables.

πŸ•·οΈ Known Issues:

  • Lua 5.1 lacks the API to access varargs. The workaround is to do something like local args = {...} and then use unpack(args) when you want to access them. In Lua 5.2+ and LuaJIT, you can simply use ... in your expressions with the print command.
  • You can't add breakpoints to a running program or remove them. Currently the only way to set them is by explicitly calling the dbg() function explicitly in your code. (This is sort of by design and sort of because it's difficult/slow otherwise.)
  • Different interpreters (and versions) print out slightly different stack trace information.
  • Tail calls are handled silghtly differently in different interpreters. You may find that 1.) stepping into a function that does nothing but a tail call steps you into the tail called function. 2.) The interpreter gives you the wrong name of a tail called function (watch the line numbers). 3.) Stepping out of a tail called function also steps out of the function that performed the tail call. Mostly this is never a problem, but it is a little confusing if you don't know what is going on.
  • Coroutine support has not been tested extensively yet, and Lua vs. LuaJIT handle them differently anyway. -_-

πŸͺ° License:

Copyright (c) 2024 Scott Lembcke and Howling Moon Software

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

debugger.lua's People

Contributors

achalddave avatar aleclarson avatar churay avatar lua-rocks avatar n-r-k avatar nayruden avatar patrickdahlin avatar slembcke avatar suy avatar thirtythreeforty avatar turbo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

debugger.lua's Issues

Variable Assignment

Thanks a lot for this great tool!
Until now I haven't been able to figure out how to do variable assignment e.g. a=5 in the debugger environment. Is this a valid functionality?

no command for repeating the above commands

I want to run a previous command (e.g., p some_tablex[2]) without typing it again, I just pressed the up arrow from the keyboard and hoped the above commands would be copied automatically. However, it didn't work. It would be very helpful to add this function, as every time typing in everything is quite tedious.

n, f commands don't work

I am on Ubuntu 18.04 using lua 5.2.4.

While running the tutorial.lua file, the following output occurred (skipping the beginning):

    Now try the 'n' command.
    (n = step to the Next line in the source code)

tutorial.lua:85 in 'tutorial.lua:0'
debugger.lua> n
tutorial.lua:60 in 'func2'
debugger.lua> n
You used the 'n' command.
tutorial.lua:61 in 'func2'
debugger.lua> n
So it's skipping over the lines in func2().
tutorial.lua:65 in 'func2'
debugger.lua> n
tutorial.lua:67 in 'func2'
debugger.lua> n
tutorial.lua:64 in 'f'
debugger.lua> n
... and anything it might call.

But of course, I expected:

    Now try the 'n' command.
    (n = step to the Next line in the source code)

./tutorial.lua:85 in '<./tutorial.lua:0>'
debugger.lua> n
You used the 'n' command.
So it's skipping over the lines in func2().
... and anything it might call.

Thus, it seems like n steps into functions just like s does. Likewise, using f inside a function simply did the same as c instead of stopping right after the function.

I assumed n, f worked in the past, so I used git bisect and found that 4cc1189 introduced this error (n, f worked fine before 4cc1189).

In particular, this line (line 142 in 4cc1189):
return info.source:match('^@[%.%/]') ~= nil
matches only files starting with ./

Then I noticed, that n, f work properly if I run the tutorial with

lua ./tutorial.lua

but not with

lua tutorial.lua

Because the latter does not contain ./ and aforementioned line of code does not find a match.

Maybe the pattern '^@[%.%/]' needs to be adapted? Or is the prefix './' always required?

(n)ext and (s)tep should behave different after using (u)p

Let's say I moved up the stack by a frame. Now, I want to skip to the (n)ext line of the frame I'm inspecting. The current behavior simply skips the line of the bottom frame (even if I moved up a frame).

Would this be easy to implement? πŸ˜„

dbg_writeln() error.

From an email:

Thanks for writing the Lua debugger. I use it in my control program for my model
trains.

It was trivial to install and by overwriting the dbg.* functions, I was able to seamless
integrate it into my program.

I did come across one odd behavior. I had this function in my code:

   function cleanup(Engine, TIU)
            -- this function is called once when the user presses the [STOP] button
            --print(os.date("cleanup()  %d %b %Y   %X"));
            return true;            -- false=cleanup failed, true=cleanup succeeded
   end

A commented out "print()" command that I had for testing only.

I entered the debugger with "dbg()" and then used the "s(tep)" command
to step into this function and through it.As I stepped through lines
68 an then line 69 I got this error message:

68 -- this function is called once when the user presses the [STOP] button
Error 2 running function cleanup() : E:\Projects\RTC\lua\debugger.lua:110: bad argument #3 to 'format' (no value)

Here is the dbg_writeln() in debugger.lua that I overwrote (I don't need the "\n"):

   local function dbg_writeln(str, ...)
      if select("#", ...) == 0 then
            -- mcd change for RTC Debugging
            --dbg.write((str or "<NULL>").."\n")
            dbg.write((str or "<NULL>"))
            -- mcd change for RTC Debugging
      else
            -- mcd change for RTC Debugging
            --dbg.write(string.format(str.."\n", ...))
            dbg.write(string.format(str, ...))                      ------- this is line 110 refered to in the error       
            -- mcd change for RTC Debugging
      end
   end

I appears that the "%" in the commented out print() statement is confusing
the string.format() call inside of dbg_writeln().

I set "dbg.auto_where = 2" for the line tracing.

This can be worked around but you might want to fix it.

Thanks again, Mark

Command history

Sometimes I want to reuse something I typed before. How hard would it be to add command history? Should I look around for another Lua script that does it, and send it your way?

^[[A (up arrow) goes to the next older command (if one exists)

^[[B (down arrow) goes to the next newer command, where an empty line is always last

History would persist across debugger sessions.

jump?

Whether it can be directly picked to a line?

Automated tests

This is something I could probably work on with the time I have available. The low hanging fruit is to at least automate the tutorial as a test. Maybe start some simple unit tests beyond that.

Caret controls

The ability to effectively edit the current command would be great. Here are the key bindings:

^[[D (left arrow): move the caret one character to the left, unless at the beginning

^[[C (right arrow): move the caret one character to the right, unless at the end

^A: move the caret to the beginning

^E: move the caret to the end

Step/next/continue broken after merging #28

Sorry I haven't been super active with helping to test. :-\

Stepping through the tutorial is currently broken. For example, if you check out 2df69^, follow the tutorial, all seems to be well. 2df69 doesn't work for me any more on lua 5.3.1 or luajit 2.0.3, and gives different behaviors. I also tried 5.3.3 on a different machine and got the same 5.3.1 behavior.

Should we revert the commit for now or do you think you have a pretty good idea of what is wrong? (Sorry, it looks like you put a lot of work into that one. :-\ )

Coroutines

It would be great if using finish or next within a coroutine would avoid triggering the REPL outside that coroutine (unless the coroutine dies). You can always use step if you want to follow yields.

Command for exiting the program

A shortcut for p os.exit() would be appreciated.

^C may be a good option if you can capture keyboard input.

^D seems to exit the debugger, which works great.

loadstring

Is it possible to debug functions created with loadstring?

Could not get it to work with love2d

Probably this debugger was not even meant to work with love2d, but I'm reporting here because it would be great to be able to get it working there!

When running dbg(), I get the Loaded for LuaJIT 2.0.5 message in the terminal and after that the program keeps running instead of the debugger kicking in. Also, the program runs with very poor performance after this, as if it's doing something in the background.

Edit: forgot to mention love version: 11.1

Ability to change pretty output function

Hello.

Currently, debugger.lua uses it's own "recurse" function which gets pretty good results, but if I use metaclass, it defines a "__tostring" meta-method which results in this being printed out:

debugger.lua> p self.tileMap
self.tileMap => instance of class TileMap

However, if I swap "recurse" with my own "print" function, I can get something like this

debugger.lua> p self.tileMap
self.tileMap => <TileMap>{
  layers = {
    Above = <TileMapLayer>{
      tiles = {},
      z = 1
    },
    Below = <TileMapLayer>{
      tiles = {
        ["0,0"] = <Tile>{
          id = 0,
          tid = 4
        },
        ...
      },
      z = 0
    },
    Collision = <TileMapLayer>{
      tiles = {},
      z = -1
    }
  },
  tilesets = {
    [4] = <Tileset>{
      texture = sol.::LuaTexture: 0x555ad6cb0878
    }
  }
}

So, it would be pretty nice if dbg has a configurable "prettyPrint" function, which you could override. :)
If you're OK with the idea, I can make a PR.

features

first of all my congratulations on the project, I tested it here, and it worked which is a beauty.

I would like to suggest some modifications like:

colors:

  • in addition to the environment variable uses or a init function;
  • defines a background color by default black / gray is not 'too visible'
...
_G.DBG_NOCOLOR = true;
dbg = dofile('debugger.lua')
dbg.init( {'color' = false} )
...

accept callback:

  • dbg.read(prompt)
  • dbg.write(str)

[UX Suggestion] Add "command not found" error message?

I come from a Ruby background and am used to pry and byebug as my main debuggers, and spent a good 20mins wondering why I couldn't inspect variables with this debugger when stepping/moving worked fine.

I was doing variations of this:

debugger.lua> transitions
debugger.lua> src_state
debugger.lua> print src_state
debugger.lua> locals
debugger.lua> l
	dest_state => "Ready"
	src_state => "Not Ready"
	transitions => {"Not Ready" = {}}

I realize that l prints all the locals out, but I'm wondering how I can inspect individual variables...

And then out of frustration I type in:

debugger.lua> asdfasdf
debugger.lua>

And notice that the prompt doesn't react and then I'm like ohhh when it doesn't understand a command it just pretends like nothing happened.

Cue these attempts:

debugger.lua> pp(transitions)
debugger.lua> p
Error: command 'p' not recognized.
Type 'h' and press return for a command list.
debugger.lua> pp
debugger.lua> h
[.. help output ..]
debugger.lua> p transitions
transitions => {"Not Ready" = {}}

I was expecting the output of p and pp to tell me what arguments they were expecting.

Even more confusing is that when you do type in the right single character command but with the wrong arguments, it says Error: command 'p' not recognized - which is arguably even more misleading 🀣

Anyway. Just throwing my 2 cents in here. I'm still too much of a lua noob to attempt to fix this myself, but if you think it's a worthy addition, I can make an attempt.

Edit: Turned out to be an easy fix - I created a PR.

bad return type

in file debugger.c.lua, line 108

bool err = lua_pcall(lua, nargs, nresults, msgh);

'bool' must be replace by 'int', otherwise some errors are missed

Problem with arrow keys

This bug is not related to your code but maybe you know how to fix it.

It happens on Xubuntu 20.04, xfce4-terminal, on any lua version (5.1-5.4), including luajit.

In debugger.lua prompt I can't use arrow keys. When I press them, it types annoying bullshit like this: "^[[D^[[A^[[C^[[B".

Start debugger when entering/returning from function

I have tried to run the debugger at the entry of a function with the given name, using the following (slow) code. This works with debug.debug() but not with dbg(). Is there a recommended way to do this? Note that calling dbg() within debug.debug() does not work either (assuming dbg() is visible).

local dbg = require 'debugger'
local function dbgfun (fun, typ_)
  local function hook (event)
    local info = debug.getinfo(2,'n')
    if info.name == fun then
      io.write("dbg: ", info.name, "\n")
      return debug.debug() -- tail call
--    return dbg()         -- tail call
    end
  end
  debug.sethook(hook, typ_ or "call")
end
dbgfun("myfun")

cannot resolve symbol 'isatty'

Does this debugger work on windows 10. I'm currently running win 10 with luajit. I get the following error message.

Error: lib/debugger.lua:448: cannot resolve symbol 'isatty': The specified procedure could not be found.

stack traceback:
[C]: in function '__index'
lib/debugger.lua:448: in main chunk
[C]: in function 'require'

Truncate long string properties

When inspecting a table, it sometimes has a property whose value is a long string that fills up the terminal. This is often too noisy. If I wanted to see the entire string, I would inspect that specific property.

Truncation would replace the end of the string with ... (# = 1000). This could be colored so it's clear this isn't the string's real value.

What would be a good length for truncation?

problem with lua 5.3.3

I get this error PANIC: unprotected error in call to Lua API (attempt to index a function value) inside function dbg_setup() when setting readFunct param, inside function lua_setfield(lua, -1, "read");

License

Hi, could you choose and publish a license for this project? I want to use it in my game, but our packaging requirements require that all files have an explicit license from the author.

If you don't particularly care, I'd suggest the MIT license since it's compatible with nearly every organization's licensing requirements.

Great work on the library, hope to get a response soon.

Keys command

Some command for viewing the keys of a table would be very useful.

Autocomplete for tables

A nice feature would be linenoise autocomplete for table keys.

We just need a function that uses pairs on the table and its __index chain (assuming all __index values are tables).

Debug uncaught errors

A method that sets a debug hook that triggers the REPL on any uncaught errors would be useful, because you wouldn't have to reactively edit a dbg.call into your code when an unexpected error occurs.

Not decided on the method name, but I think this would be pretty cool! πŸ‘

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.