debug_cmd.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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. }
  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. } else {
  130. debugger.RemoveBreakPoint(args[0], -1)
  131. }
  132. return nil, nil
  133. }
  134. /*
  135. DocString returns a descriptive text about this command.
  136. */
  137. func (c *rmBreakpointCommand) DocString() string {
  138. return "Remove a breakpoint specifying <source>:<line>"
  139. }
  140. // disablebreak
  141. // ============
  142. /*
  143. disableBreakpointCommand temporarily disables a breakpoint
  144. */
  145. type disableBreakpointCommand struct {
  146. *inbuildDebugCommand
  147. }
  148. /*
  149. Execute the debug command and return its result. It must be possible to
  150. convert the output data into a JSON string.
  151. */
  152. func (c *disableBreakpointCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  153. if len(args) == 0 {
  154. return nil, fmt.Errorf("Need a break target (<source>:<line>) as first parameter")
  155. }
  156. targetSplit := strings.Split(args[0], ":")
  157. if len(targetSplit) > 1 {
  158. if line, err := strconv.Atoi(targetSplit[1]); err == nil {
  159. debugger.DisableBreakPoint(targetSplit[0], line)
  160. return nil, nil
  161. }
  162. }
  163. return nil, fmt.Errorf("Invalid break target - should be <source>:<line>")
  164. }
  165. /*
  166. DocString returns a descriptive text about this command.
  167. */
  168. func (c *disableBreakpointCommand) DocString() string {
  169. return "Temporarily disable a breakpoint specifying <source>:<line>"
  170. }
  171. // cont
  172. // ====
  173. /*
  174. contCommand continues a suspended thread
  175. */
  176. type contCommand struct {
  177. *inbuildDebugCommand
  178. }
  179. /*
  180. Execute the debug command and return its result. It must be possible to
  181. convert the output data into a JSON string.
  182. */
  183. func (c *contCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  184. var cmd util.ContType
  185. if len(args) != 2 {
  186. return nil, fmt.Errorf("Need a thread ID and a command Resume, StepIn, StepOver or StepOut")
  187. }
  188. threadID, err := c.AssertNumParam(1, args[0])
  189. if err == nil {
  190. cmdString := strings.ToLower(args[1])
  191. switch cmdString {
  192. case "resume":
  193. cmd = util.Resume
  194. case "stepin":
  195. cmd = util.StepIn
  196. case "stepover":
  197. cmd = util.StepOver
  198. case "stepout":
  199. cmd = util.StepOut
  200. default:
  201. return nil, fmt.Errorf("Invalid command %v - must be resume, stepin, stepover or stepout", cmdString)
  202. }
  203. debugger.Continue(threadID, cmd)
  204. }
  205. return nil, err
  206. }
  207. /*
  208. DocString returns a descriptive text about this command.
  209. */
  210. func (c *contCommand) DocString() string {
  211. return "Continues a suspended thread. Specify <threadID> <Resume | StepIn | StepOver | StepOut>"
  212. }
  213. // describe
  214. // ========
  215. /*
  216. describeCommand describes a suspended thread
  217. */
  218. type describeCommand struct {
  219. *inbuildDebugCommand
  220. }
  221. /*
  222. Execute the debug command and return its result. It must be possible to
  223. convert the output data into a JSON string.
  224. */
  225. func (c *describeCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  226. var res interface{}
  227. if len(args) != 1 {
  228. return nil, fmt.Errorf("Need a thread ID")
  229. }
  230. threadID, err := c.AssertNumParam(1, args[0])
  231. if err == nil {
  232. res = debugger.Describe(threadID)
  233. }
  234. return res, err
  235. }
  236. /*
  237. DocString returns a descriptive text about this command.
  238. */
  239. func (c *describeCommand) DocString() string {
  240. return "Describes a suspended thread."
  241. }
  242. // status
  243. // ======
  244. /*
  245. statusCommand shows breakpoints and suspended threads
  246. */
  247. type statusCommand struct {
  248. *inbuildDebugCommand
  249. }
  250. /*
  251. Execute the debug command and return its result. It must be possible to
  252. convert the output data into a JSON string.
  253. */
  254. func (c *statusCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  255. return debugger.Status(), nil
  256. }
  257. /*
  258. DocString returns a descriptive text about this command.
  259. */
  260. func (c *statusCommand) DocString() string {
  261. return "Shows breakpoints and suspended threads."
  262. }
  263. // extract
  264. // =======
  265. /*
  266. extractCommand copies a value from a suspended thread into the
  267. global variable scope
  268. */
  269. type extractCommand struct {
  270. *inbuildDebugCommand
  271. }
  272. /*
  273. Execute the debug command and return its result. It must be possible to
  274. convert the output data into a JSON string.
  275. */
  276. func (c *extractCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  277. if len(args) != 3 {
  278. return nil, fmt.Errorf("Need a thread ID, a variable name and a destination variable name")
  279. }
  280. threadID, err := c.AssertNumParam(1, args[0])
  281. if err == nil {
  282. if !parser.NamePattern.MatchString(args[1]) || !parser.NamePattern.MatchString(args[2]) {
  283. err = fmt.Errorf("Variable names may only contain [a-zA-Z] and [a-zA-Z0-9] from the second character")
  284. }
  285. if err == nil {
  286. err = debugger.ExtractValue(threadID, args[1], args[2])
  287. }
  288. }
  289. return nil, err
  290. }
  291. /*
  292. DocString returns a descriptive text about this command.
  293. */
  294. func (c *extractCommand) DocString() string {
  295. return "Copies a value from a suspended thread into the global variable scope."
  296. }
  297. // inject
  298. // =======
  299. /*
  300. injectCommand copies a value from the global variable scope into
  301. a suspended thread
  302. */
  303. type injectCommand struct {
  304. *inbuildDebugCommand
  305. }
  306. /*
  307. Execute the debug command and return its result. It must be possible to
  308. convert the output data into a JSON string.
  309. */
  310. func (c *injectCommand) Run(debugger util.ECALDebugger, args []string) (interface{}, error) {
  311. if len(args) < 3 {
  312. return nil, fmt.Errorf("Need a thread ID, a variable name and an expression")
  313. }
  314. threadID, err := c.AssertNumParam(1, args[0])
  315. if err == nil {
  316. varName := args[1]
  317. expression := strings.Join(args[2:], " ")
  318. err = debugger.InjectValue(threadID, varName, expression)
  319. }
  320. return nil, err
  321. }
  322. /*
  323. DocString returns a descriptive text about this command.
  324. */
  325. func (c *injectCommand) DocString() string {
  326. return "Copies a value from the global variable scope into a suspended thread."
  327. }