debug.go 17 KB

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