debug.go 18 KB

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