github.com/aergoio/aergo@v1.3.1/contract/hook_dbg.go (about) 1 // +build Debug 2 3 package contract 4 5 /* 6 #include "debug.h" 7 #include <stdlib.h> 8 */ 9 import "C" 10 import ( 11 "container/list" 12 "encoding/hex" 13 "errors" 14 "fmt" 15 "path/filepath" 16 17 "github.com/aergoio/aergo/types" 18 ) 19 20 type contract_info struct { 21 contract_id_base58 string 22 src_path string 23 breakpoints *list.List 24 } 25 26 var contract_info_map = make(map[string]*contract_info) 27 var watchpoints = list.New() 28 29 func (ce *Executor) setCountHook(limit C.int) { 30 if ce == nil || ce.L == nil { 31 return 32 } 33 if ce.err != nil { 34 return 35 } 36 37 if cErrMsg := C.vm_set_debug_hook(ce.L); cErrMsg != nil { 38 errMsg := C.GoString(cErrMsg) 39 40 ctrLog.Fatal().Str("err", errMsg).Msg("Fail to initialize lua contract debugger") 41 } 42 } 43 44 func HexAddrToBase58Addr(contract_id_hex string) (string, error) { 45 byteContractID, err := hex.DecodeString(contract_id_hex) 46 if err != nil { 47 return "", err 48 } 49 50 return types.NewAccount(byteContractID).ToString(), nil 51 } 52 53 func HexAddrOrPlainStrToHexAddr(d string) string { 54 // try to convert to base58 address to test input format 55 if encodedAddr, err := HexAddrToBase58Addr(d); err == nil { 56 // try to decode the encoded str 57 if _, err = types.DecodeAddress(encodedAddr); err == nil { 58 // input is hex string. just return 59 return d 60 } 61 } 62 63 // input is a name to hashing or base58 address 64 return PlainStrToHexAddr(d) 65 } 66 67 func PlainStrToHexAddr(d string) string { 68 69 return hex.EncodeToString(strHash(d)) 70 } 71 72 func SetBreakPoint(contract_id_hex string, line uint64) error { 73 74 if HasBreakPoint(contract_id_hex, line) { 75 return errors.New("Same breakpoint already exists") 76 } 77 78 addr, err := HexAddrToBase58Addr(contract_id_hex) 79 if err != nil { 80 return err 81 } 82 83 if _, ok := contract_info_map[contract_id_hex]; !ok { 84 // create new one if not exist 85 contract_info_map[contract_id_hex] = &contract_info{ 86 addr, 87 "", 88 list.New()} 89 } 90 91 insertPoint := contract_info_map[contract_id_hex].breakpoints.Front() 92 if insertPoint != nil { 93 for { 94 nextIter := insertPoint.Next() 95 if line < insertPoint.Value.(uint64) { 96 insertPoint = nil 97 break 98 } else if nextIter == nil || line < nextIter.Value.(uint64) { 99 break 100 } 101 insertPoint = nextIter 102 } 103 } 104 105 if insertPoint == nil { 106 // line is the smallest or list is empty. insert the line to the first of the list 107 contract_info_map[contract_id_hex].breakpoints.PushFront(line) 108 } else { 109 // insert after the most biggest breakpoints among smaller ones 110 contract_info_map[contract_id_hex].breakpoints.InsertAfter(line, insertPoint) 111 } 112 113 return nil 114 } 115 116 func DelBreakPoint(contract_id_hex string, line uint64) error { 117 if !HasBreakPoint(contract_id_hex, line) { 118 return errors.New("Breakpoint does not exists") 119 } 120 121 if info, ok := contract_info_map[contract_id_hex]; ok { 122 for iter := info.breakpoints.Front(); iter != nil; iter = iter.Next() { 123 if line == iter.Value.(uint64) { 124 info.breakpoints.Remove(iter) 125 return nil 126 } 127 } 128 } 129 130 return nil 131 } 132 133 func HasBreakPoint(contract_id_hex string, line uint64) bool { 134 if info, ok := contract_info_map[contract_id_hex]; ok { 135 for iter := info.breakpoints.Front(); iter != nil; iter = iter.Next() { 136 if line == iter.Value { 137 return true 138 } 139 } 140 } 141 return false 142 } 143 144 //export PrintBreakPoints 145 func PrintBreakPoints() { 146 if len(contract_info_map) == 0 { 147 return 148 } 149 for _, info := range contract_info_map { 150 fmt.Printf("%s (%s): ", info.contract_id_base58, info.src_path) 151 for iter := info.breakpoints.Front(); iter != nil; iter = iter.Next() { 152 fmt.Printf("%d ", iter.Value) 153 } 154 fmt.Printf("\n") 155 } 156 } 157 158 //export ResetBreakPoints 159 func ResetBreakPoints() { 160 for _, info := range contract_info_map { 161 info.breakpoints = list.New() 162 } 163 } 164 165 func SetWatchPoint(code string) error { 166 if code == "" { 167 return errors.New("Empty string cannot be set") 168 } 169 170 watchpoints.PushBack(code) 171 172 return nil 173 } 174 175 func DelWatchPoint(idx uint64) error { 176 if uint64(watchpoints.Len()) < idx { 177 return errors.New("invalid index") 178 } 179 180 var i uint64 = 0 181 for e := watchpoints.Front(); e != nil; e = e.Next() { 182 i++ 183 if i >= idx { 184 watchpoints.Remove(e) 185 return nil 186 } 187 } 188 189 return nil 190 } 191 192 func ListWatchPoints() *list.List { 193 return watchpoints 194 } 195 196 //export ResetWatchPoints 197 func ResetWatchPoints() { 198 watchpoints = list.New() 199 } 200 201 func UpdateContractInfo(contract_id_hex string, path string) { 202 203 if path != "" { 204 absPath, err := filepath.Abs(path) 205 if err != nil { 206 ctrLog.Fatal().Str("path", path).Msg("Try to set a invalid path") 207 } 208 path = filepath.ToSlash(absPath) 209 } 210 211 if info, ok := contract_info_map[contract_id_hex]; ok { 212 info.src_path = path 213 214 } else { 215 addr, err := HexAddrToBase58Addr(contract_id_hex) 216 if err != nil { 217 ctrLog.Fatal().Str("contract_id_hex", contract_id_hex).Msg("Fail to Decode Hex Address") 218 } 219 contract_info_map[contract_id_hex] = &contract_info{ 220 addr, 221 path, 222 list.New()} 223 } 224 } 225 226 func ResetContractInfo() { 227 // just remove src paths. keep others for future use 228 for _, info := range contract_info_map { 229 info.src_path = "" 230 } 231 232 } 233 234 //export CGetContractID 235 func CGetContractID(contract_id_hex_c *C.char) *C.char { 236 contract_id_hex := C.GoString(contract_id_hex_c) 237 if info, ok := contract_info_map[contract_id_hex]; ok { 238 return C.CString(info.contract_id_base58) 239 } else { 240 return C.CString("") 241 } 242 } 243 244 //export CGetSrc 245 func CGetSrc(contract_id_hex_c *C.char) *C.char { 246 contract_id_hex := C.GoString(contract_id_hex_c) 247 if info, ok := contract_info_map[contract_id_hex]; ok { 248 return C.CString(info.src_path) 249 } else { 250 return C.CString("") 251 } 252 } 253 254 //export CSetBreakPoint 255 func CSetBreakPoint(contract_name_or_hex_c *C.char, line_c C.double) { 256 257 contract_name_or_hex := C.GoString(contract_name_or_hex_c) 258 line := uint64(line_c) 259 260 err := SetBreakPoint(HexAddrOrPlainStrToHexAddr(contract_name_or_hex), line) 261 if err != nil { 262 ctrLog.Error().Err(err).Msg("Fail to add breakpoint") 263 } 264 } 265 266 //export CDelBreakPoint 267 func CDelBreakPoint(contract_name_or_hex_c *C.char, line_c C.double) { 268 contract_name_or_hex := C.GoString(contract_name_or_hex_c) 269 line := uint64(line_c) 270 271 err := DelBreakPoint(HexAddrOrPlainStrToHexAddr(contract_name_or_hex), line) 272 if err != nil { 273 ctrLog.Error().Err(err).Msg("Fail to delete breakpoint") 274 } 275 } 276 277 //export CHasBreakPoint 278 func CHasBreakPoint(contract_id_hex_c *C.char, line_c C.double) C.int { 279 280 contract_id_hex := C.GoString(contract_id_hex_c) 281 line := uint64(line_c) 282 283 if HasBreakPoint(contract_id_hex, line) { 284 return C.int(1) 285 } 286 287 return C.int(0) 288 } 289 290 //export CSetWatchPoint 291 func CSetWatchPoint(code_c *C.char) { 292 code := C.GoString(code_c) 293 294 err := SetWatchPoint(code) 295 if err != nil { 296 ctrLog.Error().Err(err).Msg("Fail to set watchpoint") 297 } 298 } 299 300 //export CDelWatchPoint 301 func CDelWatchPoint(idx_c C.double) { 302 idx := uint64(idx_c) 303 304 err := DelWatchPoint(idx) 305 if err != nil { 306 ctrLog.Error().Err(err).Msg("Fail to del watchpoint") 307 } 308 } 309 310 //export CGetWatchPoint 311 func CGetWatchPoint(idx_c C.int) *C.char { 312 idx := int(idx_c) 313 var i int = 0 314 for e := watchpoints.Front(); e != nil; e = e.Next() { 315 i++ 316 if i == idx { 317 return C.CString(e.Value.(string)) 318 } 319 } 320 321 return C.CString("") 322 } 323 324 //export CLenWatchPoints 325 func CLenWatchPoints() C.int { 326 return C.int(watchpoints.Len()) 327 } 328 329 //export GetDebuggerCode 330 func GetDebuggerCode() *C.char { 331 332 return C.CString(` 333 package.preload['__debugger'] = function() 334 335 --{{{ history 336 337 --15/03/06 DCN Created based on RemDebug 338 --28/04/06 DCN Update for Lua 5.1 339 --01/06/06 DCN Fix command argument parsing 340 -- Add step/over N facility 341 -- Add trace lines facility 342 --05/06/06 DCN Add trace call/return facility 343 --06/06/06 DCN Make it behave when stepping through the creation of a coroutine 344 --06/06/06 DCN Integrate the simple debugger into the main one 345 --07/06/06 DCN Provide facility to step into coroutines 346 --13/06/06 DCN Fix bug that caused the function environment to get corrupted with the global one 347 --14/06/06 DCN Allow 'sloppy' file names when setting breakpoints 348 --04/08/06 DCN Allow for no space after command name 349 --11/08/06 DCN Use io.write not print 350 --30/08/06 DCN Allow access to array elements in 'dump' 351 --10/10/06 DCN Default to breakfile for all commands that require a filename and give '-' 352 --06/12/06 DCN Allow for punctuation characters in DUMP variable names 353 --03/01/07 DCN Add pause on/off facility 354 --19/06/07 DCN Allow for duff commands being typed in the debugger (thanks to Michael.Bringmann@lsi.com) 355 -- Allow for case sensitive file systems (thanks to Michael.Bringmann@lsi.com) 356 --04/08/09 DCN Add optional line count param to pause 357 --05/08/09 DCN Reset the debug hook in Pause() even if we think we're started 358 --30/09/09 DCN Re-jig to not use co-routines (makes debugging co-routines awkward) 359 --01/10/09 DCN Add ability to break on reaching any line in a file 360 --24/07/13 TWW Added code for emulating setfenv/getfenv in Lua 5.2 as per 361 -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html 362 --25/07/13 TWW Copied Alex Parrill's fix for errors when tracing back across a C frame 363 -- (https://github.com/ColonelThirtyTwo/clidebugger, 26/01/12) 364 --25/07/13 DCN Allow for windows and unix file name conventions in has_breakpoint 365 --26/07/13 DCN Allow for \ being interpreted as an escape inside a [] pattern in 5.2 366 --29/01/17 RMM Fix lua 5.2 and 5.3 compat, fix crash in error msg, sort help output 367 --22/03/19 Modified for Aergo Contracts 368 369 --}}} 370 --{{{ description 371 372 --A simple command line debug system for Lua written by Dave Nichols of 373 --Match-IT Limited. Its public domain software. Do with it as you wish. 374 375 --This debugger was inspired by: 376 -- RemDebug 1.0 Beta 377 -- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug) 378 379 --Usage: 380 -- require('debugger') --load the debug library 381 -- pause(message) --start/resume a debug session 382 383 --An assert() failure will also invoke the debugger. 384 385 --}}} 386 387 __debugger = {} 388 389 local coro_debugger 390 local events = { BREAK = 1, WATCH = 2, STEP = 3, SET = 4 } 391 local watches = {} 392 local step_into = false 393 local step_over = false 394 local step_lines = 0 395 local step_level = {main=0} 396 local stack_level = {main=0} 397 local trace_level = {main=0} 398 local ret_file, ret_line, ret_name 399 local current_thread = 'main' 400 local started = false 401 local _g = _G 402 local skip_pause_for_init = false 403 404 --{{{ make Lua 5.2 compatible 405 406 local unpack = unpack or table.unpack 407 local loadstring = loadstring or load 408 409 --}}} 410 411 --{{{ local hints -- command help 412 --The format in here is name=summary|description 413 local hints = { 414 415 setb = [[ 416 setb [line file] -- set a breakpoint to line/file|, line 0 means 'any' 417 418 If file is omitted or is '-'' the breakpoint is set at the file for the 419 currently set level (see 'set'). Execution pauses when this line is about 420 to be executed and the debugger session is re-activated. 421 422 The file can be given as the fully qualified name, partially qualified or 423 just the file name. E.g. if file is set as 'myfile.lua', then whenever 424 execution reaches any file that ends with 'myfile.lua' it will pause. If 425 no extension is given, any extension will do. 426 427 If the line is given as 0, then reaching any line in the file will do. 428 ]], 429 430 delb = [[ 431 delb [line file] -- removes a breakpoint| 432 433 If file is omitted or is '-'' the breakpoint is removed for the file of the 434 currently set level (see 'set'). 435 ]], 436 437 resetb = [[ 438 resetb -- removes all breakpoints| 439 ]], 440 441 setw = [[ 442 setw <exp> -- adds a new watch expression| 443 444 The expression is evaluated before each line is executed. If the expression 445 yields true then execution is paused and the debugger session re-activated. 446 The expression is executed in the context of the line about to be executed. 447 ]], 448 449 delw = [[ 450 delw <index> -- removes the watch expression at index| 451 452 The index is that returned when the watch expression was set by setw. 453 ]], 454 455 resetw = [[ 456 resetw -- removes all watch expressions| 457 ]], 458 459 run = [[ 460 run -- run until next breakpoint or watch expression| 461 ]], 462 463 step = [[ 464 step [N] -- run next N lines, stepping into function calls| 465 466 If N is omitted, use 1. 467 ]], 468 469 over = [[ 470 over [N] -- run next N lines, stepping over function calls| 471 472 If N is omitted, use 1. 473 ]], 474 475 out = [[ 476 out [N] -- run lines until stepped out of N functions| 477 478 If N is omitted, use 1. 479 If you are inside a function, using 'out 1' will run until you return 480 from that function to the caller. 481 ]], 482 483 listb = [[ 484 listb -- lists breakpoints| 485 ]], 486 487 listw = [[ 488 listw -- lists watch expressions| 489 ]], 490 491 set = [[ 492 set [level] -- set context to stack level, omitted=show| 493 494 If level is omitted it just prints the current level set. 495 This sets the current context to the level given. This affects the 496 context used for several other functions (e.g. vars). The possible 497 levels are those shown by trace. 498 ]], 499 500 vars = [[ 501 vars [depth] -- list context locals to depth, omitted=1| 502 503 If depth is omitted then uses 1. 504 Use a depth of 0 for the maximum. 505 Lists all non-nil local variables and all non-nil upvalues in the 506 currently set context. For variables that are tables, lists all fields 507 to the given depth. 508 ]], 509 510 fenv = [[ 511 fenv [depth] -- list context function env to depth, omitted=1| 512 513 If depth is omitted then uses 1. 514 Use a depth of 0 for the maximum. 515 Lists all function environment variables in the currently set context. 516 For variables that are tables, lists all fields to the given depth. 517 ]], 518 519 glob = [[ 520 glob [depth] -- list globals to depth, omitted=1| 521 522 If depth is omitted then uses 1. 523 Use a depth of 0 for the maximum. 524 Lists all global variables. 525 For variables that are tables, lists all fields to the given depth. 526 ]], 527 528 ups = [[ 529 ups -- list all the upvalue names| 530 531 These names will also be in the 'vars' list unless their value is nil. 532 This provides a means to identify which vars are upvalues and which are 533 locals. If a name is both an upvalue and a local, the local value takes 534 precedance. 535 ]], 536 537 locs = [[ 538 locs -- list all the locals names| 539 540 These names will also be in the 'vars' list unless their value is nil. 541 This provides a means to identify which vars are upvalues and which are 542 locals. If a name is both an upvalue and a local, the local value takes 543 precedance. 544 ]], 545 546 dump = [[ 547 dump <var> [depth] -- dump all fields of variable to depth| 548 549 If depth is omitted then uses 1. 550 Use a depth of 0 for the maximum. 551 Prints the value of <var> in the currently set context level. If <var> 552 is a table, lists all fields to the given depth. <var> can be just a 553 name, or name.field or name.# to any depth, e.g. t.1.f accesses field 554 'f' in array element 1 in table 't'. 555 556 Can also be called from a script as dump(var,depth). 557 ]], 558 559 trace = [[ 560 trace -- dumps a stack trace| 561 562 Format is [level] = file,line,name 563 The level is a candidate for use by the 'set' command. 564 ]], 565 566 info = [[ 567 info -- dumps the complete debug info captured| 568 569 Only useful as a diagnostic aid for the debugger itself. This information 570 can be HUGE as it dumps all variables to the maximum depth, so be careful. 571 ]], 572 573 show = [[ 574 show line file X Y -- show X lines before and Y after line in file| 575 576 If line is omitted or is '-' then the current set context line is used. 577 If file is omitted or is '-' then the current set context file is used. 578 If file is not fully qualified and cannot be opened as specified, then 579 a search for the file in the package[path] is performed using the usual 580 'require' searching rules. If no file extension is given, .lua is used. 581 Prints the lines from the source file around the given line. 582 ]], 583 584 exit = [[ 585 exit -- exits debugger, re-start it using pause()| 586 ]], 587 588 help = [[ 589 help [command] -- show this list or help for command| 590 ]], 591 592 ['<statement>'] = [[ 593 <statement> -- execute a statement in the current context| 594 595 The statement can be anything that is legal in the context, including 596 assignments. Such assignments affect the context and will be in force 597 immediately. Any results returned are printed. Use '=' as a short-hand 598 for 'return', e.g. '=func(arg)' will call 'func' with 'arg' and print 599 the results, and '=var' will just print the value of 'var'. 600 ]], 601 602 } 603 --}}} 604 605 --{{{ local function getinfo(level,field) 606 607 --like debug.getinfo but copes with no activation record at the given level 608 --and knows how to get 'field'. 'field' can be the name of any of the 609 --activation record fields or any of the 'what' names or nil for everything. 610 --only valid when using the stack level to get info, not a function name. 611 612 local function getinfo(level,field) 613 level = level + 1 --to get to the same relative level as the caller 614 if not field then return debug.getinfo(level) end 615 local what 616 if field == 'name' or field == 'namewhat' then 617 what = 'n' 618 elseif field == 'what' or field == 'source' or field == 'linedefined' or field == 'lastlinedefined' or field == 'short_src' then 619 what = 'S' 620 elseif field == 'currentline' then 621 what = 'l' 622 elseif field == 'nups' then 623 what = 'u' 624 elseif field == 'func' then 625 what = 'f' 626 else 627 return debug.getinfo(level,field) 628 end 629 local ar = debug.getinfo(level,what) 630 if ar then return ar[field] else return nil end 631 end 632 633 --}}} 634 --{{{ local function indented( level, ... ) 635 636 local function indented( level, ... ) 637 io.write( string.rep(' ',level), table.concat({...}), '\n' ) 638 end 639 640 --}}} 641 --{{{ local function dumpval( level, name, value, limit ) 642 643 local function dumpval( level, name, value, limit ) 644 local index 645 if type(name) == 'number' then 646 index = string.format('[%d] = ',name) 647 elseif type(name) == 'string' 648 and (name == '__VARSLEVEL__' or name == '__ENVIRONMENT__' or name == '__GLOBALS__' or name == '__UPVALUES__' or name == '__LOCALS__') then 649 --ignore these, they are debugger generated 650 return 651 elseif type(name) == 'string' and string.find(name,'^[_%a][_.%w]*$') then 652 index = name ..' = ' 653 else 654 index = string.format('[%q] = ',tostring(name)) 655 end 656 if type(value) == 'table' then 657 if (limit or 0) > 0 and level+1 >= limit then 658 indented( level, index, tostring(value), ';' ) 659 else 660 indented( level, index, '{' ) 661 for n,v in pairs(value) do 662 dumpval( level+1, n, v, limit ) 663 end 664 indented( level, '};' ) 665 end 666 else 667 if type(value) == 'string' then 668 if string.len(value) > 40 then 669 indented( level, index, '[[', value, ']];' ) 670 else 671 indented( level, index, string.format('%q',value), ';' ) 672 end 673 else 674 indented( level, index, tostring(value), ';' ) 675 end 676 end 677 end 678 679 --}}} 680 --{{{ local function dumpvar( value, limit, name ) 681 682 local function dumpvar( value, limit, name ) 683 dumpval( 0, name or tostring(value), value, limit ) 684 end 685 686 --}}} 687 688 --{{{ local function show(contract_id_hex,line,before,after) 689 690 --show +/-N lines of a contract source around line M 691 692 local function show(contract_id_hex,line,before,after) 693 694 line = tonumber(line or 1) 695 before = tonumber(before or 10) 696 after = tonumber(after or before) 697 local file = '' 698 local base58_addr = '' 699 700 -- find matched source from 701 _, file = __get_contract_info(contract_id_hex) 702 703 if not string.find(file,'%.') then file = file..'.lua' end 704 705 local f = io.open(file,'r') 706 if not f then 707 io.write('Cannot find '..file..' for contract '..base58_addr..'\n') 708 return 709 end 710 711 local i = 0 712 for l in f:lines() do 713 i = i + 1 714 if i >= (line-before) then 715 if i > (line+after) then break end 716 if i == line then 717 io.write(i..'***\t'..l..'\n') 718 else 719 io.write(i..'\t'..l..'\n') 720 end 721 end 722 end 723 724 f:close() 725 726 end 727 728 --}}} 729 --{{{ local function tracestack(l) 730 731 local function gi( i ) 732 return function() i=i+1 return debug.getinfo(i),i end 733 end 734 735 local function gl( level, j ) 736 return function() j=j+1 return debug.getlocal( level, j ) end 737 end 738 739 local function gu( func, k ) 740 return function() k=k+1 return debug.getupvalue( func, k ) end 741 end 742 743 local traceinfo 744 745 local function tracestack(l) 746 local l = l + 1 --NB: +1 to get level relative to caller 747 traceinfo = {} 748 --traceinfo.pausemsg = pausemsg 749 for ar,i in gi(l) do 750 table.insert( traceinfo, ar ) 751 if ar.what ~= 'C' then 752 local names = {} 753 local values = {} 754 755 for n,v in gl(i-1,0) do 756 --for n,v in gl(i,0) do 757 if string.sub(n,1,1) ~= '(' then --ignore internal control variables 758 table.insert( names, n ) 759 table.insert( values, v ) 760 end 761 end 762 if #names > 0 then 763 ar.lnames = names 764 ar.lvalues = values 765 end 766 end 767 if ar.func then 768 local names = {} 769 local values = {} 770 for n,v in gu(ar.func,0) do 771 if string.sub(n,1,1) ~= '(' then --ignore internal control variables 772 table.insert( names, n ) 773 table.insert( values, v ) 774 end 775 end 776 if #names > 0 then 777 ar.unames = names 778 ar.uvalues = values 779 end 780 end 781 end 782 end 783 784 --}}} 785 --{{{ local function trace() 786 787 local function trace(set) 788 local mark 789 for level,ar in ipairs(traceinfo) do 790 if level == set then 791 mark = '***' 792 else 793 mark = '' 794 end 795 local contract_id_base58, _ = __get_contract_info(ar.source) 796 io.write('['..level..']'..mark..'\t'..(ar.name or ar.what)..' in '..(contract_id_base58 or ar.short_src)..':'..ar.currentline..'\n') 797 end 798 end 799 800 --}}} 801 --{{{ local function info() 802 803 local function info() dumpvar( traceinfo, 0, 'traceinfo' ) end 804 805 --}}} 806 807 --}}} 808 --{{{ local function has_breakpoint(file, line) 809 810 --allow for 'sloppy' file names 811 --search for file and all variations walking up its directory hierachy 812 --ditto for the file with no extension 813 --a breakpoint can be permenant or once only, if once only its removed 814 --after detection here, these are used for temporary breakpoints in the 815 --a breakpoint on line 0 of a file means any line in that file 816 817 local function has_breakpoint(contract_id_hex, line) 818 819 return __has_breakpoint(contract_id_hex, line) 820 end 821 822 --}}} 823 --{{{ local function capture_vars(ref,level,line) 824 825 local function capture_vars(ref,level,line) 826 --get vars, contract_id_hex, contract_id_base58 and line for the given level relative to debug_hook offset by ref 827 828 local lvl = ref + level --NB: This includes an offset of +1 for the call to here 829 830 --{{{ capture variables 831 832 local ar = debug.getinfo(lvl, 'f') 833 if not ar then return {},'?','?',0 end 834 835 local vars = {__UPVALUES__={}, __LOCALS__={}} 836 local i 837 838 local func = ar.func 839 if func then 840 i = 1 841 while true do 842 local name, value = debug.getupvalue(func, i) 843 if not name then break end 844 if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables 845 vars[name] = value 846 vars.__UPVALUES__[i] = name 847 end 848 i = i + 1 849 end 850 vars.__ENVIRONMENT__ = getfenv(func) 851 end 852 853 vars.__GLOBALS__ = getfenv(0) 854 855 i = 1 856 while true do 857 local name, value = debug.getlocal(lvl, i) 858 if not name then break end 859 if string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables 860 vars[name] = value 861 vars.__LOCALS__[i] = name 862 end 863 i = i + 1 864 end 865 866 vars.__VARSLEVEL__ = level 867 868 if func then 869 --NB: Do not do this until finished filling the vars table 870 setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) }) 871 end 872 873 --NB: Do not read or write the vars table anymore else the metatable functions will get invoked! 874 875 --}}} 876 877 local contract_id_hex = getinfo(lvl, 'source') 878 if string.find(contract_id_hex, '@') == 1 then 879 contract_id_hex = string.sub(contract_id_hex, 2) 880 end 881 882 local contract_id_base58, _ = __get_contract_info(contract_id_hex) 883 884 if not line then 885 line = getinfo(lvl, 'currentline') 886 end 887 888 return vars,contract_id_hex,contract_id_base58,line 889 890 end 891 892 --}}} 893 --{{{ local function restore_vars(ref,vars) 894 895 local function restore_vars(ref,vars) 896 897 if type(vars) ~= 'table' then return end 898 899 local level = vars.__VARSLEVEL__ --NB: This level is relative to debug_hook offset by ref 900 if not level then return end 901 902 level = level + ref --NB: This includes an offset of +1 for the call to here 903 904 local i 905 local written_vars = {} 906 907 i = 1 908 while true do 909 local name, value = debug.getlocal(level, i) 910 if not name then break end 911 if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables 912 debug.setlocal(level, i, vars[name]) 913 written_vars[name] = true 914 end 915 i = i + 1 916 end 917 918 local ar = debug.getinfo(level, 'f') 919 if not ar then return end 920 921 local func = ar.func 922 if func then 923 924 i = 1 925 while true do 926 local name, value = debug.getupvalue(func, i) 927 if not name then break end 928 if vars[name] and string.sub(name,1,1) ~= '(' then --NB: ignoring internal control variables 929 if not written_vars[name] then 930 debug.setupvalue(func, i, vars[name]) 931 end 932 written_vars[name] = true 933 end 934 i = i + 1 935 end 936 937 end 938 939 end 940 941 --}}} 942 --{{{ local function trace_event(event, line, level) 943 944 local function trace_event(event, line, level) 945 946 if event ~= 'line' then return end 947 948 local slevel = stack_level[current_thread] 949 local tlevel = trace_level[current_thread] 950 951 trace_level[current_thread] = stack_level[current_thread] 952 953 end 954 955 --}}} 956 --{{{ local function report(ev, vars, file, line, idx_watch) 957 958 local function report(ev, vars, contract_id_base58, line, idx_watch) 959 local vars = vars or {} 960 local contract_id_base58 = contract_id_base58 or '?' 961 local line = line or 0 962 local prefix = '' 963 if current_thread ~= 'main' then prefix = '['..tostring(current_thread)..'] ' end 964 if ev == events.STEP then 965 io.write(prefix..'Paused at contract '..contract_id_base58..' line '..line..' ('..stack_level[current_thread]..')\n') 966 elseif ev == events.BREAK then 967 io.write(prefix..'Paused at contract '..contract_id_base58..' line '..line..' ('..stack_level[current_thread]..') (breakpoint)\n') 968 elseif ev == events.WATCH then 969 io.write(prefix..'Paused at contract '..contract_id_base58..' line '..line..' ('..stack_level[current_thread]..')'..' (watch expression '..idx_watch.. ': ['..__get_watchpoint(idx_watch)..'])\n') 970 elseif ev == events.SET then 971 --do nothing 972 else 973 io.write(prefix..'Error in application: '..contract_id_base58..' line '..line..'\n') 974 end 975 return vars, contract_id_base58, line 976 end 977 978 --}}} 979 980 --{{{ local function debugger_loop(ev, vars, file, line, idx_watch) 981 982 local function debugger_loop(ev, vars, file, line, idx_watch) 983 984 local eval_env = vars or {} 985 local breakfile = file or '?' 986 local breakline = line or 0 987 988 local command, args 989 990 --{{{ local function getargs(spec) 991 992 --get command arguments according to the given spec from the args string 993 --the spec has a single character for each argument, arguments are separated 994 --by white space, the spec characters can be one of: 995 -- F for a filename (defaults to breakfile if - given in args) 996 -- L for a line number (defaults to breakline if - given in args) 997 -- N for a number 998 -- V for a variable name 999 -- S for a string 1000 1001 local function getargs(spec) 1002 local res={} 1003 local char,arg 1004 local ptr=1 1005 for i=1,string.len(spec) do 1006 char = string.sub(spec,i,i) 1007 if char == 'F' then 1008 _,ptr,arg = string.find(args..' ','%s*([%w%p]*)%s*',ptr) 1009 if not arg or arg == '' then arg = '-' end 1010 if arg == '-' then arg = breakfile end 1011 elseif char == 'L' then 1012 _,ptr,arg = string.find(args..' ','%s*([%w%p]*)%s*',ptr) 1013 if not arg or arg == '' then arg = '-' end 1014 if arg == '-' then arg = breakline end 1015 arg = tonumber(arg) or 0 1016 elseif char == 'N' then 1017 _,ptr,arg = string.find(args..' ','%s*([%w%p]*)%s*',ptr) 1018 if not arg or arg == '' then arg = '0' end 1019 arg = tonumber(arg) or 0 1020 elseif char == 'V' then 1021 _,ptr,arg = string.find(args..' ','%s*([%w%p]*)%s*',ptr) 1022 if not arg or arg == '' then arg = '' end 1023 elseif char == 'S' then 1024 _,ptr,arg = string.find(args..' ','%s*([%w%p]*)%s*',ptr) 1025 if not arg or arg == '' then arg = '' end 1026 else 1027 arg = '' 1028 end 1029 table.insert(res,arg or '') 1030 end 1031 return unpack(res) 1032 end 1033 1034 --}}} 1035 1036 while true do 1037 io.write('[DEBUG]> ') 1038 local line = io.read('*line') 1039 if line == nil then io.write('\n'); line = 'exit' end 1040 1041 if string.find(line, '^[a-z]+') then 1042 command = string.sub(line, string.find(line, '^[a-z]+')) 1043 args = string.gsub(line,'^[a-z]+%s*','',1) --strip command off line 1044 else 1045 command = '' 1046 end 1047 1048 if command == 'setb' then 1049 --{{{ set breakpoint 1050 1051 local line, contract_id_hex = getargs('LF') 1052 if contract_id_hex ~= '' and line ~= '' then 1053 __set_breakpoint(contract_id_hex,line) 1054 else 1055 io.write('Bad request\n') 1056 end 1057 1058 --}}} 1059 1060 elseif command == 'delb' then 1061 --{{{ delete breakpoint 1062 1063 local line, contract_id_hex = getargs('LF') 1064 if contract_id_hex ~= '' and line ~= '' then 1065 __delete_breakpoint(contract_id_hex, line) 1066 else 1067 io.write('Bad request\n') 1068 end 1069 1070 --}}} 1071 1072 elseif command == 'resetb' then 1073 --{{{ delete all breakpoints 1074 --TODO 1075 io.write('All breakpoints deleted\n') 1076 --}}} 1077 1078 elseif command == 'listb' then 1079 --{{{ list breakpoints 1080 __print_breakpoints() 1081 --}}} 1082 1083 elseif command == 'setw' then 1084 --{{{ set watch expression 1085 1086 if args and args ~= '' then 1087 __set_watchpoint(args) 1088 io.write('Set watch exp no. ' .. __len_watchpoints() ..'\n') 1089 else 1090 io.write('Bad request\n') 1091 end 1092 1093 --}}} 1094 1095 elseif command == 'delw' then 1096 --{{{ delete watch expression 1097 1098 local index = tonumber(args) 1099 if index then 1100 __delete_watchpoint(index) 1101 io.write('Watch expression deleted\n') 1102 else 1103 io.write('Bad request\n') 1104 end 1105 1106 --}}} 1107 1108 elseif command == 'resetw' then 1109 --{{{ delete all watch expressions 1110 __reset_watchpoints() 1111 io.write('All watch expressions deleted\n') 1112 --}}} 1113 1114 elseif command == 'listw' then 1115 --{{{ list watch expressions 1116 for i, v in pairs(__list_watchpoints()) do 1117 io.write(i .. ': ' .. v..'\n') 1118 end 1119 --}}} 1120 1121 elseif command == 'run' then 1122 --{{{ run until breakpoint 1123 step_into = false 1124 step_over = false 1125 return 'cont' 1126 --}}} 1127 1128 elseif command == 'step' then 1129 --{{{ step N lines (into functions) 1130 local N = tonumber(args) or 1 1131 step_over = false 1132 step_into = true 1133 step_lines = tonumber(N or 1) 1134 return 'cont' 1135 --}}} 1136 1137 elseif command == 'over' then 1138 --{{{ step N lines (over functions) 1139 local N = tonumber(args) or 1 1140 step_into = false 1141 step_over = true 1142 step_lines = tonumber(N or 1) 1143 step_level[current_thread] = stack_level[current_thread] 1144 return 'cont' 1145 --}}} 1146 1147 elseif command == 'out' then 1148 --{{{ step N lines (out of functions) 1149 local N = tonumber(args) or 1 1150 step_into = false 1151 step_over = true 1152 step_lines = 1 1153 step_level[current_thread] = stack_level[current_thread] - tonumber(N or 1) 1154 return 'cont' 1155 --}}} 1156 1157 elseif command == 'set' then 1158 --{{{ set/show context level 1159 local level = args 1160 if level and level == '' then level = nil end 1161 if level then return level end 1162 --}}} 1163 1164 elseif command == 'vars' then 1165 --{{{ list context variables 1166 local depth = args 1167 if depth and depth == '' then depth = nil end 1168 depth = tonumber(depth) or 1 1169 dumpvar(eval_env, depth+1, 'variables') 1170 --}}} 1171 1172 elseif command == 'glob' then 1173 --{{{ list global variables 1174 local depth = args 1175 if depth and depth == '' then depth = nil end 1176 depth = tonumber(depth) or 1 1177 dumpvar(eval_env.__GLOBALS__,depth+1,'globals') 1178 --}}} 1179 1180 elseif command == 'fenv' then 1181 --{{{ list function environment variables 1182 local depth = args 1183 if depth and depth == '' then depth = nil end 1184 depth = tonumber(depth) or 1 1185 dumpvar(eval_env.__ENVIRONMENT__,depth+1,'environment') 1186 --}}} 1187 1188 elseif command == 'ups' then 1189 --{{{ list upvalue names 1190 dumpvar(eval_env.__UPVALUES__,2,'upvalues') 1191 --}}} 1192 1193 elseif command == 'locs' then 1194 --{{{ list locals names 1195 dumpvar(eval_env.__LOCALS__,2,'upvalues') 1196 --}}} 1197 1198 elseif command == 'dump' then 1199 --{{{ dump a variable 1200 local name, depth = getargs('VN') 1201 if name ~= '' then 1202 if depth == '' or depth == 0 then depth = nil end 1203 depth = tonumber(depth or 1) 1204 local v = eval_env 1205 local n = nil 1206 for w in string.gmatch(name,'[^%.]+') do --get everything between dots 1207 if tonumber(w) then 1208 v = v[tonumber(w)] 1209 else 1210 v = v[w] 1211 end 1212 if n then n = n..'.'..w else n = w end 1213 if not v then break end 1214 end 1215 dumpvar(v,depth+1,n) 1216 else 1217 io.write('Bad request\n') 1218 end 1219 --}}} 1220 1221 elseif command == 'show' then 1222 --{{{ show contract around a line or the current breakpoint 1223 local line, contract_id_hex, before, after = getargs('LFNN') 1224 if before == 0 then before = 10 end 1225 if after == 0 then after = before end 1226 1227 if contract_id_hex ~= '' and contract_id_hex ~= '=stdin' then 1228 show(contract_id_hex,line,before,after) 1229 else 1230 io.write('Nothing to show\n') 1231 end 1232 --}}} 1233 1234 elseif command == 'trace' then 1235 --{{{ dump a stack trace 1236 trace(eval_env.__VARSLEVEL__) 1237 --}}} 1238 1239 elseif command == 'info' then 1240 --{{{ dump all debug info captured 1241 info() 1242 --}}} 1243 1244 elseif command == 'pause' then 1245 --{{{ not allowed in here 1246 io.write('pause() should only be used in the script you are debugging\n') 1247 --}}} 1248 1249 elseif command == 'help' then 1250 --{{{ help 1251 local command = getargs('S') 1252 if command ~= '' and hints[command] then 1253 io.write(hints[command]..'\n') 1254 else 1255 local l = {} 1256 for k,v in pairs(hints) do 1257 local _,_,h = string.find(v,'(.+)|') 1258 l[#l+1] = h..'\n' 1259 end 1260 table.sort(l) 1261 io.write(table.concat(l)) 1262 end 1263 --}}} 1264 1265 elseif command == 'exit' then 1266 --{{{ exit debugger 1267 return 'stop' 1268 --}}} 1269 1270 elseif line ~= '' then 1271 --{{{ just execute whatever it is in the current context 1272 1273 --map line starting with '=...' to 'return ...' 1274 if string.sub(line,1,1) == '=' then line = string.gsub(line,'=','return ',1) end 1275 1276 local ok, func = pcall(loadstring,line) 1277 if ok and func==nil then -- auto-print variables 1278 ok, func = pcall(loadstring,'io.write(tostring(' .. line .. '))') 1279 end 1280 if func == nil then --Michael.Bringmann@lsi.com 1281 io.write('Compile error: '..line..'\n') 1282 elseif not ok then 1283 io.write('Compile error: '..func..'\n') 1284 else 1285 setfenv(func, eval_env) 1286 local res = {pcall(func)} 1287 if res[1] then 1288 if res[2] then 1289 table.remove(res,1) 1290 for _,v in ipairs(res) do 1291 io.write(tostring(v)) 1292 io.write('\t') 1293 end 1294 end 1295 --update in the context 1296 io.write('\n') 1297 return 0 1298 else 1299 io.write('Run error: '..res[2]..'\n') 1300 end 1301 end 1302 1303 --}}} 1304 end 1305 end 1306 1307 end 1308 1309 --}}} 1310 --{{{ local function debug_hook(event, line, level, thread) 1311 local function debug_hook(event, line, level, thread) 1312 if not started then debug.sethook(); coro_debugger = nil; return end 1313 current_thread = thread or 'main' 1314 local level = level or 2 1315 trace_event(event,line,level) 1316 if event == 'line' then 1317 -- calculate current stack 1318 for i=1,99999,1 do 1319 if not debug.getinfo(i) then break end 1320 stack_level[current_thread] = i - 1 -- minus one to remove this debug_hook stack 1321 end 1322 1323 local vars,contract_id_hex,contract_id_base58,line = capture_vars(level,1,line) 1324 local stop, ev, idx = false, events.STEP, 0 1325 while true do 1326 for index, value in pairs(__list_watchpoints()) do 1327 local func = loadstring('return(' .. value .. ')') 1328 if func ~= nil then 1329 setfenv(func, vars) 1330 local status, res = pcall(func) 1331 if status and res then 1332 ev, idx = events.WATCH, index 1333 stop = true 1334 break 1335 end 1336 end 1337 end 1338 if stop then break end 1339 if (step_into) 1340 or (step_over and (stack_level[current_thread] <= step_level[current_thread] or stack_level[current_thread] == 0)) then 1341 step_lines = step_lines - 1 1342 if step_lines < 1 then 1343 ev, idx = events.STEP, 0 1344 break 1345 end 1346 end 1347 if has_breakpoint(contract_id_hex, line) then 1348 ev, idx = events.BREAK, 0 1349 break 1350 end 1351 return 1352 end 1353 if skip_pause_for_init then 1354 --DO notthing 1355 elseif not coro_debugger then 1356 io.write('Lua Debugger\n') 1357 vars, contract_id_base58, line = report(ev, vars, contract_id_base58, line, idx) 1358 io.write('Type \'help\' for commands\n') 1359 coro_debugger = true 1360 else 1361 vars, contract_id_base58, line = report(ev, vars, contract_id_base58, line, idx) 1362 end 1363 tracestack(level) 1364 local last_next = 1 1365 local next = 'ask' 1366 local silent = false 1367 while true do 1368 if next == 'ask' then 1369 if skip_pause_for_init then 1370 step_into = false 1371 --step_over = false 1372 skip_pause_for_init = false -- reset flag 1373 return -- for the first time 1374 end 1375 next = debugger_loop(ev, vars, contract_id_hex, line, idx) 1376 elseif next == 'cont' then 1377 return 1378 elseif next == 'stop' then 1379 started = false 1380 debug.sethook() 1381 coro_debugger = nil 1382 return 1383 elseif tonumber(next) then --get vars for given level or last level 1384 next = tonumber(next) 1385 if next == 0 then silent = true; next = last_next else silent = false end 1386 last_next = next 1387 restore_vars(level,vars) 1388 vars, contract_id_hex, contract_id_base58, line = capture_vars(level,next) 1389 if not silent then 1390 if vars and vars.__VARSLEVEL__ then 1391 io.write('Level: '..vars.__VARSLEVEL__..'\n') 1392 else 1393 io.write('No level set\n') 1394 end 1395 end 1396 ev = events.SET 1397 next = 'ask' 1398 else 1399 io.write('Unknown command from debugger_loop: '..tostring(next)..'\n') 1400 io.write('Stopping debugger\n') 1401 next = 'stop' 1402 end 1403 end 1404 end 1405 end 1406 1407 1408 --{{{ function hook() 1409 1410 -- 1411 -- Init and Register Debug Hook 1412 -- 1413 --function hook() 1414 1415 function __debugger.hook() 1416 --set to stop when get out of pause() 1417 trace_level[current_thread] = 0 1418 step_level [current_thread] = 0 1419 stack_level[current_thread] = 1 1420 step_lines = 1 1421 step_into = true 1422 started = true 1423 skip_pause_for_init = true 1424 1425 debug.sethook(debug_hook, 'l') 1426 end 1427 1428 --}}} 1429 --{{{ function dump(v,depth) 1430 1431 --shows the value of the given variable, only really useful 1432 --when the variable is a table 1433 --see dump debug command hints for full semantics 1434 1435 local function dump(v,depth) 1436 dumpvar(v,(depth or 1)+1,tostring(v)) 1437 end 1438 1439 --}}} 1440 1441 return __debugger 1442 end 1443 1444 require('__debugger').hook() 1445 `) 1446 }