debug.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /*
  2. * ECAL
  3. *
  4. * Copyright 2020 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. /*
  11. Package interpreter contains the ECAL interpreter.
  12. */
  13. package interpreter
  14. import (
  15. "fmt"
  16. "strings"
  17. "sync"
  18. "devt.de/krotik/common/datautil"
  19. "devt.de/krotik/common/errorutil"
  20. "devt.de/krotik/ecal/parser"
  21. "devt.de/krotik/ecal/scope"
  22. "devt.de/krotik/ecal/util"
  23. )
  24. /*
  25. ecalDebugger is the inbuild default debugger.
  26. */
  27. type ecalDebugger struct {
  28. breakPoints map[string]bool // Break points (active or not)
  29. interrogationStates map[uint64]*interrogationState // Collection of threads which are interrogated
  30. callStacks map[uint64][]*parser.ASTNode // Call stacks of threads
  31. sources map[string]bool // All known sources
  32. breakOnStart bool // Flag to stop at the start of the next execution
  33. globalScope parser.Scope // Global variable scope which can be used to transfer data
  34. lock *sync.RWMutex // Lock for this debugger
  35. }
  36. /*
  37. interrogationState contains state information of a thread interrogation.
  38. */
  39. type interrogationState struct {
  40. cond *sync.Cond // Condition on which the thread is waiting when suspended
  41. running bool // Flag if the thread is running or waiting
  42. cmd interrogationCmd // Next interrogation command for the thread
  43. stepOutStack []*parser.ASTNode // Target stack when doing a step out
  44. node *parser.ASTNode // Node on which the thread was last stopped
  45. vs parser.Scope // Variable scope of the thread when it was last stopped
  46. }
  47. /*
  48. interrogationCmd represents a command for a thread interrogation.
  49. */
  50. type interrogationCmd int
  51. /*
  52. Interrogation commands
  53. */
  54. const (
  55. Stop interrogationCmd = iota // Stop the execution (default)
  56. StepIn // Step into the next function
  57. StepOut // Step out of the current function
  58. StepOver // Step over the next function
  59. Resume // Resume execution - do not break again on the same line
  60. )
  61. /*
  62. newInterrogationState creates a new interrogation state.
  63. */
  64. func newInterrogationState(node *parser.ASTNode, vs parser.Scope) *interrogationState {
  65. return &interrogationState{
  66. sync.NewCond(&sync.Mutex{}),
  67. false,
  68. Stop,
  69. nil,
  70. node,
  71. vs,
  72. }
  73. }
  74. /*
  75. NewDebugger returns a new debugger object.
  76. */
  77. func NewECALDebugger(globalVS parser.Scope) util.ECALDebugger {
  78. return &ecalDebugger{
  79. breakPoints: make(map[string]bool),
  80. interrogationStates: make(map[uint64]*interrogationState),
  81. callStacks: make(map[uint64][]*parser.ASTNode),
  82. sources: make(map[string]bool),
  83. breakOnStart: false,
  84. globalScope: globalVS,
  85. lock: &sync.RWMutex{},
  86. }
  87. }
  88. /*
  89. HandleInput handles a given debug instruction from a console.
  90. */
  91. func (ed *ecalDebugger) HandleInput(input string) (interface{}, error) {
  92. var res interface{}
  93. var err error
  94. args := strings.Fields(input)
  95. if len(args) > 0 {
  96. if cmd, ok := DebugCommandsMap[args[0]]; ok {
  97. if len(args) > 1 {
  98. res, err = cmd.Run(ed, args[1:])
  99. } else {
  100. res, err = cmd.Run(ed, nil)
  101. }
  102. } else {
  103. err = fmt.Errorf("Unknown command: %v", args[0])
  104. }
  105. }
  106. return res, err
  107. }
  108. /*
  109. Break on the start of the next execution.
  110. */
  111. func (ed *ecalDebugger) BreakOnStart(flag bool) {
  112. ed.lock.Lock()
  113. defer ed.lock.Unlock()
  114. ed.breakOnStart = flag
  115. }
  116. /*
  117. VisitState is called for every state during the execution of a program.
  118. */
  119. func (ed *ecalDebugger) VisitState(node *parser.ASTNode, vs parser.Scope, tid uint64) util.TraceableRuntimeError {
  120. ed.lock.RLock()
  121. _, ok := ed.callStacks[tid]
  122. ed.lock.RUnlock()
  123. if !ok {
  124. // Make the debugger aware of running threads
  125. ed.lock.Lock()
  126. ed.callStacks[tid] = make([]*parser.ASTNode, 0, 10)
  127. ed.lock.Unlock()
  128. }
  129. if node.Token != nil { // Statements are excluded here
  130. targetIdentifier := fmt.Sprintf("%v:%v", node.Token.Lsource, node.Token.Lline)
  131. ed.lock.RLock()
  132. is, ok := ed.interrogationStates[tid]
  133. _, sourceKnown := ed.sources[node.Token.Lsource]
  134. ed.lock.RUnlock()
  135. if !sourceKnown {
  136. ed.RecordSource(node.Token.Lsource)
  137. }
  138. if ok {
  139. // The thread is being interrogated
  140. switch is.cmd {
  141. case Resume:
  142. if is.node.Token.Lline != node.Token.Lline {
  143. // Remove the resume command once we are on a different line
  144. ed.lock.Lock()
  145. delete(ed.interrogationStates, tid)
  146. ed.lock.Unlock()
  147. return ed.VisitState(node, vs, tid)
  148. }
  149. case Stop, StepIn, StepOver:
  150. if is.node.Token.Lline != node.Token.Lline || is.cmd == Stop {
  151. is.node = node
  152. is.vs = vs
  153. is.running = false
  154. is.cond.L.Lock()
  155. is.cond.Wait()
  156. is.cond.L.Unlock()
  157. }
  158. }
  159. } else if active, ok := ed.breakPoints[targetIdentifier]; (ok && active) || ed.breakOnStart {
  160. // A globally defined breakpoint has been hit - note the position
  161. // in the thread specific map and wait
  162. is := newInterrogationState(node, vs)
  163. ed.lock.Lock()
  164. ed.breakOnStart = false
  165. ed.interrogationStates[tid] = is
  166. ed.lock.Unlock()
  167. is.cond.L.Lock()
  168. is.cond.Wait()
  169. is.cond.L.Unlock()
  170. }
  171. }
  172. return nil
  173. }
  174. /*
  175. VisitStepInState is called before entering a function call.
  176. */
  177. func (ed *ecalDebugger) VisitStepInState(node *parser.ASTNode, vs parser.Scope, tid uint64) util.TraceableRuntimeError {
  178. ed.lock.Lock()
  179. defer ed.lock.Unlock()
  180. var err util.TraceableRuntimeError
  181. threadCallStack := ed.callStacks[tid]
  182. is, ok := ed.interrogationStates[tid]
  183. if ok {
  184. if is.cmd == Stop {
  185. // Special case a parameter of a function was resolved by another
  186. // function call - the debugger should stop before entering
  187. ed.lock.Unlock()
  188. err = ed.VisitState(node, vs, tid)
  189. ed.lock.Lock()
  190. }
  191. if err == nil {
  192. // The thread is being interrogated
  193. switch is.cmd {
  194. case StepIn:
  195. is.cmd = Stop
  196. case StepOver:
  197. is.cmd = StepOut
  198. is.stepOutStack = ed.callStacks[tid]
  199. }
  200. }
  201. }
  202. ed.callStacks[tid] = append(threadCallStack, node)
  203. return err
  204. }
  205. /*
  206. VisitStepOutState is called after returning from a function call.
  207. */
  208. func (ed *ecalDebugger) VisitStepOutState(node *parser.ASTNode, vs parser.Scope, tid uint64) util.TraceableRuntimeError {
  209. ed.lock.Lock()
  210. defer ed.lock.Unlock()
  211. threadCallStack := ed.callStacks[tid]
  212. lastIndex := len(threadCallStack) - 1
  213. ok, cerr := threadCallStack[lastIndex].Equals(node, false) // Sanity check step in node must be the same as step out node
  214. errorutil.AssertTrue(ok,
  215. fmt.Sprintf("Unexpected callstack when stepping out - callstack: %v - funccall: %v - comparison error: %v",
  216. threadCallStack, node, cerr))
  217. ed.callStacks[tid] = threadCallStack[:lastIndex] // Remove the last item
  218. is, ok := ed.interrogationStates[tid]
  219. if ok {
  220. // The thread is being interrogated
  221. switch is.cmd {
  222. case StepOver, StepOut:
  223. if len(ed.callStacks[tid]) == len(is.stepOutStack) {
  224. is.cmd = Stop
  225. }
  226. }
  227. }
  228. return nil
  229. }
  230. /*
  231. RecordSource records a code source.
  232. */
  233. func (ed *ecalDebugger) RecordSource(source string) {
  234. ed.lock.Lock()
  235. defer ed.lock.Unlock()
  236. ed.sources[source] = true
  237. }
  238. /*
  239. SetBreakPoint sets a break point.
  240. */
  241. func (ed *ecalDebugger) SetBreakPoint(source string, line int) {
  242. ed.lock.Lock()
  243. defer ed.lock.Unlock()
  244. ed.breakPoints[fmt.Sprintf("%v:%v", source, line)] = true
  245. }
  246. /*
  247. DisableBreakPoint disables a break point but keeps the code reference.
  248. */
  249. func (ed *ecalDebugger) DisableBreakPoint(source string, line int) {
  250. ed.lock.Lock()
  251. defer ed.lock.Unlock()
  252. ed.breakPoints[fmt.Sprintf("%v:%v", source, line)] = false
  253. }
  254. /*
  255. RemoveBreakPoint removes a break point.
  256. */
  257. func (ed *ecalDebugger) RemoveBreakPoint(source string, line int) {
  258. ed.lock.Lock()
  259. defer ed.lock.Unlock()
  260. if line > 0 {
  261. delete(ed.breakPoints, fmt.Sprintf("%v:%v", source, line))
  262. } else {
  263. for k := range ed.breakPoints {
  264. if ksource := strings.Split(k, ":")[0]; ksource == source {
  265. delete(ed.breakPoints, k)
  266. }
  267. }
  268. }
  269. }
  270. /*
  271. ExtractValue copies a value from a suspended thread into the
  272. global variable scope.
  273. */
  274. func (ed *ecalDebugger) ExtractValue(threadId uint64, varName string, destVarName string) error {
  275. if ed.globalScope == nil {
  276. return fmt.Errorf("Cannot access global scope")
  277. }
  278. err := fmt.Errorf("Cannot find suspended thread %v", threadId)
  279. ed.lock.Lock()
  280. defer ed.lock.Unlock()
  281. is, ok := ed.interrogationStates[threadId]
  282. if ok && !is.running {
  283. var val interface{}
  284. var ok bool
  285. if val, ok, err = is.vs.GetValue(varName); ok {
  286. err = ed.globalScope.SetValue(destVarName, val)
  287. } else if err == nil {
  288. err = fmt.Errorf("No such value %v", varName)
  289. }
  290. }
  291. return err
  292. }
  293. /*
  294. InjectValue copies a value from an expression (using the global variable scope) into
  295. a suspended thread.
  296. */
  297. func (ed *ecalDebugger) InjectValue(threadId uint64, varName string, expression string) error {
  298. if ed.globalScope == nil {
  299. return fmt.Errorf("Cannot access global scope")
  300. }
  301. err := fmt.Errorf("Cannot find suspended thread %v", threadId)
  302. ed.lock.Lock()
  303. defer ed.lock.Unlock()
  304. is, ok := ed.interrogationStates[threadId]
  305. if ok && !is.running {
  306. var ast *parser.ASTNode
  307. var val interface{}
  308. // Eval expression
  309. ast, err = parser.ParseWithRuntime("InjectValueExpression", expression,
  310. NewECALRuntimeProvider("InjectValueExpression2", nil, nil))
  311. if err == nil {
  312. if err = ast.Runtime.Validate(); err == nil {
  313. ivs := scope.NewScopeWithParent("InjectValueExpressionScope", ed.globalScope)
  314. val, err = ast.Runtime.Eval(ivs, make(map[string]interface{}), 999)
  315. if err == nil {
  316. err = is.vs.SetValue(varName, val)
  317. }
  318. }
  319. }
  320. }
  321. return err
  322. }
  323. /*
  324. Continue will continue a suspended thread.
  325. */
  326. func (ed *ecalDebugger) Continue(threadId uint64, contType util.ContType) {
  327. ed.lock.RLock()
  328. defer ed.lock.RUnlock()
  329. if is, ok := ed.interrogationStates[threadId]; ok && !is.running {
  330. switch contType {
  331. case util.Resume:
  332. is.cmd = Resume
  333. case util.StepIn:
  334. is.cmd = StepIn
  335. case util.StepOver:
  336. is.cmd = StepOver
  337. case util.StepOut:
  338. is.cmd = StepOut
  339. stack := ed.callStacks[threadId]
  340. is.stepOutStack = stack[:len(stack)-1]
  341. }
  342. is.running = true
  343. is.cond.L.Lock()
  344. is.cond.Broadcast()
  345. is.cond.L.Unlock()
  346. }
  347. }
  348. /*
  349. Status returns the current status of the debugger.
  350. */
  351. func (ed *ecalDebugger) Status() interface{} {
  352. ed.lock.RLock()
  353. defer ed.lock.RUnlock()
  354. var sources []string
  355. threadStates := make(map[string]map[string]interface{})
  356. res := map[string]interface{}{
  357. "breakpoints": ed.breakPoints,
  358. "breakonstart": ed.breakOnStart,
  359. "threads": threadStates,
  360. }
  361. for k := range ed.sources {
  362. sources = append(sources, k)
  363. }
  364. res["sources"] = sources
  365. for k, v := range ed.callStacks {
  366. s := map[string]interface{}{
  367. "callStack": ed.prettyPrintCallStack(v),
  368. }
  369. if is, ok := ed.interrogationStates[k]; ok {
  370. s["threadRunning"] = is.running
  371. }
  372. threadStates[fmt.Sprint(k)] = s
  373. }
  374. return res
  375. }
  376. /*
  377. Describe decribes a thread currently observed by the debugger.
  378. */
  379. func (ed *ecalDebugger) Describe(threadId uint64) interface{} {
  380. ed.lock.RLock()
  381. defer ed.lock.RUnlock()
  382. var res map[string]interface{}
  383. threadCallStack, ok1 := ed.callStacks[threadId]
  384. if is, ok2 := ed.interrogationStates[threadId]; ok1 && ok2 {
  385. res = map[string]interface{}{
  386. "threadRunning": is.running,
  387. "callStack": ed.prettyPrintCallStack(threadCallStack),
  388. }
  389. vsValues := make(map[string]interface{})
  390. parent := is.vs.Parent()
  391. for parent != nil &&
  392. parent.Name() != scope.GlobalScope &&
  393. strings.HasPrefix(parent.Name(), scope.FuncPrefix) {
  394. vsValues = datautil.MergeMaps(vsValues, parent.ToJSONObject())
  395. parent = parent.Parent()
  396. }
  397. vsValues = ed.MergeMaps(vsValues, is.vs.ToJSONObject())
  398. if !is.running {
  399. codeString, _ := parser.PrettyPrint(is.node)
  400. res["code"] = codeString
  401. res["node"] = is.node.ToJSONObject()
  402. res["vs"] = vsValues
  403. }
  404. }
  405. return res
  406. }
  407. /*
  408. MergeMaps merges all given maps into a new map. Contents are shallow copies
  409. and conflicts are resolved as first-one-wins.
  410. */
  411. func (ed *ecalDebugger) MergeMaps(maps ...map[string]interface{}) map[string]interface{} {
  412. ret := make(map[string]interface{})
  413. for _, m := range maps {
  414. for k, v := range m {
  415. if _, ok := ret[k]; !ok {
  416. ret[k] = v
  417. }
  418. }
  419. }
  420. return ret
  421. }
  422. /*
  423. Describe decribes a thread currently observed by the debugger.
  424. */
  425. func (ed *ecalDebugger) prettyPrintCallStack(threadCallStack []*parser.ASTNode) []string {
  426. cs := []string{}
  427. for _, s := range threadCallStack {
  428. pp, _ := parser.PrettyPrint(s)
  429. cs = append(cs, fmt.Sprintf("%v (%v:%v)",
  430. pp, s.Token.Lsource, s.Token.Lline))
  431. }
  432. return cs
  433. }