debug.go 15 KB

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