├── .gitignore ├── .gitmodules ├── Makefile ├── test ├── fiber_test.lua ├── test.lua └── jump_fiber.test.lua ├── LICENSE ├── README.md └── src ├── utils.lua └── tdb.lua /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ansicolors"] 2 | path = ansicolors 3 | url = https://github.com/hoelzro/ansicolors 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p build 3 | cp src/*.lua build 4 | cp ansicolors/ansicolors.lua build 5 | 6 | install: 7 | cp build/*.lua $(prefix)/ 8 | 9 | 10 | clean: 11 | rm -rf build 12 | -------------------------------------------------------------------------------- /test/fiber_test.lua: -------------------------------------------------------------------------------- 1 | tdb = require('tdb') 2 | tdb.start() 3 | 4 | 5 | fiber = require('fiber') 6 | function f() 7 | local i = 0 8 | while true do 9 | i = i + 1 10 | fiber.sleep(0.5) 11 | end 12 | end 13 | 14 | fiber.create(f) 15 | 16 | local a = 1 17 | local b = 2 18 | local t = {x=1, y=2} 19 | c = a + b 20 | print(c) 21 | 22 | local sum = 0 23 | for i=1,3 do 24 | sum = sum + i 25 | end 26 | print(sum) 27 | 28 | os.exit(0) 29 | -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | local tst = 1 2 | 3 | print(debug.getlocal(1,1)) 4 | 5 | tdb = require('tdb') 6 | tdb.start() 7 | 8 | -- function def 9 | function f(x) 10 | return x*x 11 | end 12 | 13 | -- locals 14 | local a = 1 15 | local b = 2 16 | 17 | -- tables 18 | local t = {x=1, y=2} 19 | 20 | -- globals 21 | c = a + b 22 | print(c) 23 | 24 | -- func call 25 | print(f(c)) 26 | 27 | -- flow controls 28 | local sum = 0 29 | for i=1,3 do 30 | sum = sum + i 31 | end 32 | print(sum) 33 | os.exit(0) 34 | -------------------------------------------------------------------------------- /test/jump_fiber.test.lua: -------------------------------------------------------------------------------- 1 | fiber = require('fiber') 2 | function f() 3 | -- check that tdb created in fiber 4 | -- do not jumps into the others fibers or main 5 | require('tdb').start() 6 | fiber.name('f1(inspected)') 7 | local i = 0 8 | while true do 9 | i = i + 1 10 | fiber.sleep(0.5) 11 | end 12 | end 13 | function f2() 14 | fiber.name('f2') 15 | local j = 0 16 | while true do 17 | j = j + 1 18 | fiber.sleep(0.1) 19 | end 20 | end 21 | 22 | fiber.create(f2) 23 | fiber.create(f) 24 | 25 | while true do 26 | fiber.sleep(0.1) 27 | end 28 | 29 | os.exit(0) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tdb 2 | [Tarantool](https://github.com/tarantool/tarantool) database interactive debugger. Compatible with: 3 | * 1.6.7 4 | * 1.6.8 5 | * 1.7.x 6 | 7 | 8 | ### Features: 9 | * Code navigation 10 | * Locals and globals watch 11 | * Working with tarantool fibers 12 | * Eval lua code in current execution context 13 | * Backtrace info 14 | 15 | ### Install 16 | ``` 17 | git clone --recursive https://github.com/Sulverus/tdb 18 | cd tdb 19 | make 20 | sudo make install prefix=/usr/share/tarantool/ 21 | ``` 22 | 23 | ### Usage 24 | Anywhere in lua code you can set a breakpoint 25 | ```lua 26 | require('tdb').start() 27 | ``` 28 | 29 | **Commands:** 30 | 31 | * **n** - next line 32 | * **c** - continue 33 | * **bt** - traceback 34 | * **locals** - get local context 35 | * **globals** - get global scope 36 | * **e** - enter to eval mode 37 | * **-e** - return to default mode 38 | * **f** - fiber info 39 | * **q** - exit 40 | * **h** - help 41 | 42 | **Eval mode** 43 | 44 | In eval mode you can run lua code in current execution context 45 | ``` 46 | (TDB) 7: local i = 0 47 | (TDB)> 48 | (TDB) 8: while true do 49 | (TDB)> 50 | (TDB) 9: i = i + 1 51 | (TDB)> 52 | (TDB) 10: fiber.sleep(0.5) 53 | (TDB)> 54 | (TDB) 8: while true do 55 | (TDB)>e 56 | (TDB) Eval mode ON 57 | (TDB)>return i * 2 58 | 2 59 | (TDB)> 60 | ``` 61 | 62 | 63 | ### How it works 64 | Example for `test/test.lua` interactive debugging 65 | ``` 66 | $ tarantool test.lua 67 | (TDB) Tarantool debugger v.0.0.3. Type h for help 68 | (TDB) [/home/Sulverus/tdb/test/test.lua] 69 | (TDB) 14: local a = 1 70 | (TDB)> 71 | (TDB) 15: local b = 2 72 | (TDB)> 73 | (TDB) 18: local t = {x=1, y=2} 74 | (TDB)> 75 | (TDB) 21: c = a + b 76 | (TDB)> 77 | (TDB) 22: print(c) 78 | (TDB)>locals 79 | b 2 80 | (*temporary) line 81 | t table: 0x0f316b68 82 | a 1 83 | (TDB)> 84 | 3 85 | (TDB) 25: print(f(c)) 86 | (TDB)> 87 | 9 88 | (TDB) 28: local sum = 0 89 | (TDB)> 90 | (TDB) 29: for i=1,3 do 91 | (TDB)> 92 | (TDB) 30: sum = sum + i 93 | (TDB)> 94 | (TDB) 29: for i=1,3 do 95 | (TDB)>locals 96 | t table: 0x0f316b68 97 | (for step) 1 98 | (for limit) 3 99 | b 2 100 | sum 1 101 | (*temporary) 1 102 | (for index) 1 103 | a 1 104 | (TDB)>e 105 | (TDB) Eval mode ON 106 | (TDB)>a 107 | a 1 108 | (TDB)>b 109 | b 2 110 | (TDB)>c 111 | c 3 112 | (TDB)>sum 113 | sum 1 114 | (TDB)>t 115 | table: 0x0f316b68: 116 | y 2 117 | x 1 118 | (TDB)>print(a+b+c) 119 | 6 120 | 121 | (TDB)>-e 122 | (TDB) Eval mode OFF 123 | (TDB)>c 124 | (TDB) 30: sum = sum + i 125 | (TDB) 29: for i=1,3 do 126 | (TDB) 30: sum = sum + i 127 | (TDB) 29: for i=1,3 do 128 | (TDB) 32: print(sum) 129 | 6 130 | (TDB) 33: os.exit(0) 131 | 132 | ``` 133 | -------------------------------------------------------------------------------- /src/utils.lua: -------------------------------------------------------------------------------- 1 | local colors = require('ansicolors') 2 | local fiber = require('fiber') 3 | 4 | 5 | local path_regex = "(.-)([^\\/]-%.?([^%.\\/]*))$" 6 | local function logger(msg, color, eoln) 7 | local line = colors.blue .. '(TDB)' 8 | if eoln == nil then 9 | line = line .. ' ' 10 | end 11 | line = line .. colors.reset 12 | 13 | if color ~= nil then 14 | msg = color .. msg .. colors.reset 15 | end 16 | 17 | if eoln == nil then 18 | msg = msg .. '\n' 19 | end 20 | 21 | io.output(io.stdout) 22 | io.write(line .. msg) 23 | end 24 | 25 | local function trim(s) 26 | return s:match("^%s*(.-)%s*$") 27 | end 28 | 29 | local gfind = string.gfind or string.gmatch 30 | 31 | local function print_table(t) 32 | for key, val in pairs(t) do 33 | print(key, val) 34 | end 35 | end 36 | 37 | local function print_in_table(t, key) 38 | local elem = rawget(t, key) 39 | if elem == nil then 40 | return 41 | end 42 | local elem = t[key] 43 | if type(elem) == 'table' then 44 | print(tostring(elem) ..':') 45 | print_table(elem) 46 | else 47 | print(key, elem) 48 | end 49 | end 50 | 51 | function split(str, delim) 52 | if string.find(str, delim) == nil then 53 | return { str } 54 | end 55 | local result,pat,lastpos = {},"(.-)" .. delim .. "()",nil 56 | for part, pos in gfind(str, pat) do 57 | table.insert(result, part) 58 | lastpos = pos 59 | end 60 | table.insert(result, string.sub(str, lastpos)) 61 | return result 62 | end 63 | 64 | local function globals() 65 | return _G 66 | end 67 | 68 | local function locals(context) 69 | -- find stack level 70 | local level = 1 71 | for i, s in pairs(split(debug.traceback(), '\n')) do 72 | local filename = trim(split(s, ':')[1]) 73 | 74 | local _, file, _ = string.match(filename, path_regex) 75 | local _, src, _ = string.match(context.short_src, path_regex) 76 | if file == src then 77 | level = i - 1 78 | break 79 | end 80 | end 81 | 82 | -- collect all variables 83 | local variables = {} 84 | local idx = 1 85 | while true do 86 | local ln, lv = debug.getlocal(level, idx) 87 | if ln ~= nil then 88 | variables[ln] = lv 89 | else 90 | break 91 | end 92 | idx = 1 + idx 93 | end 94 | return variables 95 | end 96 | 97 | 98 | local function find_in_scope(key, context) 99 | return rawget(locals(context), key) ~= nil or 100 | rawget(globals(), key) ~= nil 101 | end 102 | 103 | local function get_variable(key, context) 104 | print_in_table(globals(), key) 105 | print_in_table(locals(context), key) 106 | end 107 | 108 | local function get_env(cont) 109 | local context = {} 110 | for k,v in pairs(globals()) do 111 | context[k] = v 112 | end 113 | for k,v in pairs(locals(cont)) do 114 | context[k] = v 115 | end 116 | return context 117 | end 118 | 119 | local function fibers_ui() 120 | local info = fiber.info() 121 | logger('Running fibers list:', colors.blue) 122 | logger('id\tname\t\t\tmemory, %') 123 | logger('--------------------------------------------------') 124 | for id, f in pairs(info) do 125 | local memory = 100 * tonumber(f.memory.used/f.memory.total) 126 | local msg = tostring(id) .. '\t' .. f.name 127 | msg = msg .. '\t\t' .. tostring(memory) 128 | logger(msg) 129 | end 130 | logger('--------------------------------------------------') 131 | end 132 | 133 | return { 134 | logger = logger, 135 | globals = globals, 136 | find_in_scope = find_in_scope, 137 | get_variable = get_variable, 138 | get_env = get_env, 139 | print_table = print_table, 140 | fibers_ui = fibers_ui, 141 | split = split, 142 | trim = trim, 143 | locals = locals, 144 | path_regex = path_regex 145 | } 146 | -- vim: ts=4:sw=4:sts=4:et 147 | -------------------------------------------------------------------------------- /src/tdb.lua: -------------------------------------------------------------------------------- 1 | local fiber = require('fiber') 2 | local socket = require('socket') 3 | local yaml = require('yaml') 4 | local colors = require('ansicolors') 5 | local utils = require 'utils' 6 | 7 | local VERSION = 'v.0.0.3' 8 | 9 | -- source code cache 10 | local files = {} 11 | 12 | -- debugger state 13 | local cur_file = nil 14 | local next_line = false 15 | local run_mode = false 16 | local eval_mode = false 17 | local context = {} 18 | 19 | local function load_file(fname) 20 | io.input(fname) 21 | files[fname] = {} 22 | local count = 1 23 | while true do 24 | local line = io.read() 25 | if line == nil then 26 | break 27 | end 28 | files[fname][count] = line 29 | count = count + 1 30 | end 31 | end 32 | 33 | -- UI 34 | local commands = { 35 | n = function() 36 | next_line = true 37 | end; 38 | q = function() 39 | os.exit(0) 40 | end; 41 | h = function() 42 | utils.logger( 43 | [[ 44 | Help: 45 | n - next line 46 | c - continue 47 | bt - traceback 48 | locals - get local context 49 | globals - get global scope 50 | e - enter to eval mode 51 | -e - return to default mode 52 | f - fiber info 53 | q - exit 54 | h - help 55 | ]], colors.green 56 | ) 57 | end; 58 | bt = function() 59 | utils.logger(debug.traceback(), colors.red) 60 | end; 61 | c = function() 62 | run_mode = true 63 | next_line = true 64 | end; 65 | f = function() 66 | utils.fibers_ui() 67 | end; 68 | locals = function() 69 | utils.print_table(utils.locals(context)) 70 | end; 71 | globals = function() 72 | utils.print_table(utils.globals()) 73 | end; 74 | e = function() 75 | eval_mode = true 76 | utils.logger('Eval mode ON', colors.green) 77 | end; 78 | } 79 | 80 | -- check that running code is in context 81 | local function is_traced_code(s) 82 | if s.short_src == nil then 83 | return false 84 | end 85 | local path, file, _ = string.match(s.short_src, utils.path_regex) 86 | local path2, arg_file, _ = string.match(arg[0], utils.path_regex) 87 | 88 | return s.func == context.func and file == arg_file 89 | end 90 | 91 | -- exec lua string in current execution context 92 | local function eval(cmd) 93 | local status, err = pcall(function() 94 | local f = assert(loadstring(cmd)) 95 | print(setfenv(f, utils.get_env(context))()) 96 | end) 97 | if not status then 98 | utils.logger(err, colors.red) 99 | end 100 | end 101 | 102 | -- main loop 103 | local function tdb_loop() 104 | next_line = false 105 | utils.logger('>', colors.blue, true) 106 | 107 | local cmd = io.read('*line') 108 | if cmd == '' then 109 | return 1 110 | end 111 | 112 | if eval_mode then 113 | if cmd == '-e' then 114 | eval_mode = false 115 | utils.logger('Eval mode OFF', colors.green) 116 | return 117 | end 118 | 119 | if utils.find_in_scope(cmd, context) then 120 | utils.get_variable(cmd, context) 121 | else 122 | eval(cmd) 123 | end 124 | else 125 | if commands[cmd] == nil then 126 | utils.logger('Unknown command. Type h for help.', colors.red) 127 | else 128 | commands[cmd]() 129 | end 130 | end 131 | 132 | if next_line then 133 | return 1 134 | end 135 | end 136 | 137 | 138 | -- tdb init function 139 | local function debugger(event, line) 140 | local s = debug.getinfo(2) 141 | s.short_src = utils.split(s.source, '@')[2] 142 | if not is_traced_code(s)then 143 | --print('isn not traced') 144 | return 145 | end 146 | 147 | -- load file to sources cache 148 | if files[s.short_src] == nil then 149 | print(s.short_src) 150 | load_file(s.short_src) 151 | end 152 | 153 | -- set current code file 154 | if cur_file ~= s.short_src then 155 | cur_file = s.short_src 156 | utils.logger('[' .. cur_file .. ']', colors.green) 157 | end 158 | 159 | -- print code line 160 | local code = files[s.short_src][line] 161 | utils.logger(line .. ': ' .. code:gsub("^%s*(.-)%s*$", "%1")) 162 | io.input(io.stdin) 163 | 164 | -- handle 'continue' command 165 | if run_mode then 166 | return 167 | end 168 | 169 | -- man debugger loop 170 | while true do 171 | local exit = tdb_loop() 172 | if exit ~= nil then 173 | break 174 | end 175 | end 176 | end 177 | 178 | -- UI sethock wrapper 179 | local function start(info) 180 | utils.logger('Tarantool debugger '.. VERSION 181 | ..'. Type h for help', colors.green) 182 | debug.sethook(debugger, 'l') 183 | context = debug.getinfo(2) 184 | -- fix large filenames 185 | context.short_src = utils.split(context.source, '@')[2] 186 | end 187 | 188 | return { 189 | start=start; 190 | } 191 | -- vim: ts=4:sw=4:sts=4:et 192 | --------------------------------------------------------------------------------