debug.go 12 KB

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