debug_cmd.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. "strconv"
  17. "strings"
  18. "devt.de/krotik/ecal/parser"
  19. "devt.de/krotik/ecal/util"
  20. )
  21. /*
  22. DebugCommandsMap contains the mapping of inbuild debug commands.
  23. */
  24. var DebugCommandsMap = map[string]util.DebugCommand{
  25. "breakonstart": &breakOnStartCommand{&inbuildDebugCommand{}},
  26. "break": &setBreakpointCommand{&inbuildDebugCommand{}},
  27. "rmbreak": &rmBreakpointCommand{&inbuildDebugCommand{}},
  28. "disablebreak": &disableBreakpointCommand{&inbuildDebugCommand{}},
  29. "cont": &contCommand{&inbuildDebugCommand{}},
  30. "describe": &describeCommand{&inbuildDebugCommand{}},
  31. "status": &statusCommand{&inbuildDebugCommand{}},
  32. "extract": &extractCommand{&inbuildDebugCommand{}},
  33. "inject": &injectCommand{&inbuildDebugCommand{}},
  34. "lockstate": &lockstateCommand{&inbuildDebugCommand{}},
  35. }
  36. /*
  37. inbuildDebugCommand is the base structure for inbuild debug commands providing some
  38. utility functions.
  39. */
  40. type inbuildDebugCommand struct {
  41. }
  42. /*
  43. AssertNumParam converts a parameter into a number.
  44. */
  45. func (ibf *inbuildDebugCommand) AssertNumParam(index int, val string) (uint64, error) {
  46. if resNum, err := strconv.ParseInt(fmt.Sprint(val), 10, 0); err == nil {
  47. return uint64(resNum), nil
  48. }
  49. return 0, fmt.Errorf("Parameter %v should be a number", index)
  50. }
  51. // break
  52. // =====
  53. /*
  54. setBreakpointCommand sets a breakpoint
  55. */
  56. type setBreakpointCommand struct {
  57. *inbuildDebugCommand
  58. }
  59. /*
  60. Execute the debug command and return its result. It must be possible to
  61. convert the output data into a JSON string.
  62. */
  63. func (c *setBreakpointCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  64. if len(args) == 0 {
  65. return nil, fmt.Errorf("Need a break target (<source>:<line>) as first parameter")
  66. }
  67. targetSplit := strings.Split(args[0], ":")
  68. if len(targetSplit) > 1 {
  69. if line, err := strconv.Atoi(targetSplit[1]); err == nil {
  70. debugger.SetBreakPoint(targetSplit[0], line)
  71. return nil, nil
  72. }
  73. }
  74. return nil, fmt.Errorf("Invalid break target - should be <source>:<line>")
  75. }
  76. /*
  77. DocString returns a descriptive text about this command.
  78. */
  79. func (c *setBreakpointCommand) DocString() string {
  80. return "Set a breakpoint specifying <source>:<line>"
  81. }
  82. // breakOnStartCommand
  83. // ===================
  84. /*
  85. breakOnStartCommand breaks on the start of the next execution.
  86. */
  87. type breakOnStartCommand struct {
  88. *inbuildDebugCommand
  89. }
  90. /*
  91. Execute the debug command and return its result. It must be possible to
  92. convert the output data into a JSON string.
  93. */
  94. func (c *breakOnStartCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  95. b := true
  96. if len(args) > 0 {
  97. b, _ = strconv.ParseBool(args[0])
  98. }
  99. debugger.BreakOnStart(b)
  100. return nil, nil
  101. }
  102. /*
  103. DocString returns a descriptive text about this command.
  104. */
  105. func (c *breakOnStartCommand) DocString() string {
  106. return "Break on the start of the next execution."
  107. }
  108. // rmbreak
  109. // =======
  110. /*
  111. rmBreakpointCommand removes a breakpoint
  112. */
  113. type rmBreakpointCommand struct {
  114. *inbuildDebugCommand
  115. }
  116. /*
  117. Execute the debug command and return its result. It must be possible to
  118. convert the output data into a JSON string.
  119. */
  120. func (c *rmBreakpointCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  121. if len(args) == 0 {
  122. return nil, fmt.Errorf("Need a break target (<source>[:<line>]) as first parameter")
  123. }
  124. targetSplit := strings.Split(args[0], ":")
  125. if len(targetSplit) > 1 {
  126. if line, err := strconv.Atoi(targetSplit[1]); err == nil {
  127. debugger.RemoveBreakPoint(targetSplit[0], line)
  128. return nil, nil
  129. }
  130. } else {
  131. debugger.RemoveBreakPoint(args[0], -1)
  132. }
  133. return nil, nil
  134. }
  135. /*
  136. DocString returns a descriptive text about this command.
  137. */
  138. func (c *rmBreakpointCommand) DocString() string {
  139. return "Remove a breakpoint specifying <source>:<line>"
  140. }
  141. // disablebreak
  142. // ============
  143. /*
  144. disableBreakpointCommand temporarily disables a breakpoint
  145. */
  146. type disableBreakpointCommand struct {
  147. *inbuildDebugCommand
  148. }
  149. /*
  150. Execute the debug command and return its result. It must be possible to
  151. convert the output data into a JSON string.
  152. */
  153. func (c *disableBreakpointCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  154. if len(args) == 0 {
  155. return nil, fmt.Errorf("Need a break target (<source>:<line>) as first parameter")
  156. }
  157. targetSplit := strings.Split(args[0], ":")
  158. if len(targetSplit) > 1 {
  159. if line, err := strconv.Atoi(targetSplit[1]); err == nil {
  160. debugger.DisableBreakPoint(targetSplit[0], line)
  161. return nil, nil
  162. }
  163. }
  164. return nil, fmt.Errorf("Invalid break target - should be <source>:<line>")
  165. }
  166. /*
  167. DocString returns a descriptive text about this command.
  168. */
  169. func (c *disableBreakpointCommand) DocString() string {
  170. return "Temporarily disable a breakpoint specifying <source>:<line>"
  171. }
  172. // cont
  173. // ====
  174. /*
  175. contCommand continues a suspended thread
  176. */
  177. type contCommand struct {
  178. *inbuildDebugCommand
  179. }
  180. /*
  181. Execute the debug command and return its result. It must be possible to
  182. convert the output data into a JSON string.
  183. */
  184. func (c *contCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  185. var cmd util.ContType
  186. if len(args) != 2 {
  187. return nil, fmt.Errorf("Need a thread ID and a command Resume, StepIn, StepOver or StepOut")
  188. }
  189. threadID, err := c.AssertNumParam(1, args[0])
  190. if err == nil {
  191. cmdString := strings.ToLower(args[1])
  192. switch cmdString {
  193. case "resume":
  194. cmd = util.Resume
  195. case "stepin":
  196. cmd = util.StepIn
  197. case "stepover":
  198. cmd = util.StepOver
  199. case "stepout":
  200. cmd = util.StepOut
  201. default:
  202. return nil, fmt.Errorf("Invalid command %v - must be resume, stepin, stepover or stepout", cmdString)
  203. }
  204. debugger.Continue(threadID, cmd)
  205. }
  206. return nil, err
  207. }
  208. /*
  209. DocString returns a descriptive text about this command.
  210. */
  211. func (c *contCommand) DocString() string {
  212. return "Continues a suspended thread. Specify <threadID> <Resume | StepIn | StepOver | StepOut>"
  213. }
  214. // describe
  215. // ========
  216. /*
  217. describeCommand describes a suspended thread
  218. */
  219. type describeCommand struct {
  220. *inbuildDebugCommand
  221. }
  222. /*
  223. Execute the debug command and return its result. It must be possible to
  224. convert the output data into a JSON string.
  225. */
  226. func (c *describeCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  227. var res interface{}
  228. if len(args) != 1 {
  229. return nil, fmt.Errorf("Need a thread ID")
  230. }
  231. threadID, err := c.AssertNumParam(1, args[0])
  232. if err == nil {
  233. res = debugger.Describe(threadID)
  234. }
  235. return res, err
  236. }
  237. /*
  238. DocString returns a descriptive text about this command.
  239. */
  240. func (c *describeCommand) DocString() string {
  241. return "Describes a suspended thread."
  242. }
  243. // status
  244. // ======
  245. /*
  246. statusCommand shows breakpoints and suspended threads
  247. */
  248. type statusCommand struct {
  249. *inbuildDebugCommand
  250. }
  251. /*
  252. Execute the debug command and return its result. It must be possible to
  253. convert the output data into a JSON string.
  254. */
  255. func (c *statusCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  256. return debugger.Status(), nil
  257. }
  258. /*
  259. DocString returns a descriptive text about this command.
  260. */
  261. func (c *statusCommand) DocString() string {
  262. return "Shows breakpoints and suspended threads."
  263. }
  264. // extract
  265. // =======
  266. /*
  267. extractCommand copies a value from a suspended thread into the
  268. global variable scope
  269. */
  270. type extractCommand struct {
  271. *inbuildDebugCommand
  272. }
  273. /*
  274. Execute the debug command and return its result. It must be possible to
  275. convert the output data into a JSON string.
  276. */
  277. func (c *extractCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  278. if len(args) != 3 {
  279. return nil, fmt.Errorf("Need a thread ID, a variable name and a destination variable name")
  280. }
  281. threadID, err := c.AssertNumParam(1, args[0])
  282. if err == nil {
  283. if !parser.NamePattern.MatchString(args[1]) || !parser.NamePattern.MatchString(args[2]) {
  284. err = fmt.Errorf("Variable names may only contain [a-zA-Z] and [a-zA-Z0-9] from the second character")
  285. }
  286. if err == nil {
  287. err = debugger.ExtractValue(threadID, args[1], args[2])
  288. }
  289. }
  290. return nil, err
  291. }
  292. /*
  293. DocString returns a descriptive text about this command.
  294. */
  295. func (c *extractCommand) DocString() string {
  296. return "Copies a value from a suspended thread into the global variable scope."
  297. }
  298. // inject
  299. // =======
  300. /*
  301. injectCommand copies a value from the global variable scope into
  302. a suspended thread
  303. */
  304. type injectCommand struct {
  305. *inbuildDebugCommand
  306. }
  307. /*
  308. Execute the debug command and return its result. It must be possible to
  309. convert the output data into a JSON string.
  310. */
  311. func (c *injectCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  312. if len(args) < 3 {
  313. return nil, fmt.Errorf("Need a thread ID, a variable name and an expression")
  314. }
  315. threadID, err := c.AssertNumParam(1, args[0])
  316. if err == nil {
  317. varName := args[1]
  318. expression := strings.Join(args[2:], " ")
  319. err = debugger.InjectValue(threadID, varName, expression)
  320. }
  321. return nil, err
  322. }
  323. /*
  324. DocString returns a descriptive text about this command.
  325. */
  326. func (c *injectCommand) DocString() string {
  327. return "Copies a value from the global variable scope into a suspended thread."
  328. }
  329. // lockstate
  330. // =========
  331. /*
  332. lockstateCommand inspects the locking state
  333. */
  334. type lockstateCommand struct {
  335. *inbuildDebugCommand
  336. }
  337. /*
  338. Execute the debug command and return its result. It must be possible to
  339. convert the output data into a JSON string.
  340. */
  341. func (c *lockstateCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  342. return debugger.LockState(), nil
  343. }
  344. /*
  345. DocString returns a descriptive text about this command.
  346. */
  347. func (c *lockstateCommand) DocString() string {
  348. return "Inspects the locking state."
  349. }