debug.go 16 KB

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