debug.go 13 KB

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