debug_cmd.go 9.3 KB

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