init; based on: https://github.com/lldb-tools/lldb-mi
[llgdb.git] / src / LLGDBMain.cpp
1 //===-- LLGDBMain.cpp -------------------------------------------*- C++ -*-===//
2 //
3 //
4 //===----------------------------------------------------------------------===//
5
6 #include <lldb/API/SBHostOS.h>
7 #include <llvm/ADT/StringRef.h>
8 #include <llvm/Support/PrettyStackTrace.h>
9 #include <lldb/API/SBDebugger.h>
10 #include <lldb/Utility/StreamString.h>
11 #include <llvm/Support/Signals.h>
12 #include <lldb/API/SBCommandInterpreter.h>
13 #include <lldb/API/SBCommandReturnObject.h>
14 #include <lldb/API/SBStringList.h>
15 #include <boost/format.hpp>
16 #include <readline/readline.h>
17 #include <readline/history.h>
18 #include <vector>
19 #include <deque>
20 #include <setjmp.h>
21 #include <signal.h>
22 #include <error.h>
23
24 using namespace lldb;
25 using namespace lldb_private;
26
27 static jmp_buf llgdb_readline_jmp_buf;
28
29 static void llgdb_readline_sigint_handler(int signum) {
30   assert(signum == SIGINT);
31   longjmp(llgdb_readline_jmp_buf, 1);
32 }
33
34 static char *llgdb_readline(const char *prompt) {
35   char *retval;
36   sighandler_t saved_handler;
37   sigset_t mask;
38   int i;
39
40   i = sigprocmask(SIG_SETMASK, NULL, &mask);
41   assert(i == 0);
42   saved_handler = signal(SIGINT, llgdb_readline_sigint_handler);
43   if (setjmp(llgdb_readline_jmp_buf) != 0) {
44     rl_free_line_state();
45     rl_cleanup_after_signal();
46
47     /* GDB prints this.  */
48     puts("Quit");
49
50     i = sigprocmask(SIG_SETMASK, &mask, NULL);
51     assert(i == 0);
52   }
53   retval = readline(prompt);
54   saved_handler = signal(SIGINT, saved_handler);
55   assert(saved_handler == llgdb_readline_sigint_handler);
56   return retval;
57 }
58
59 #if 0
60 static int console_cb_rows, console_cb_columns, console_cb_row,
61     console_cb_column;
62 static bool console_cb_drop;
63
64 static void console_cb(const char *str, void *data) {
65   const char *cs;
66
67   if (console_cb_drop)
68     return;
69   if (console_cb_rows == 0) {
70     fputs(str, stdout);
71     return;
72   }
73
74   while (*str != '\0') {
75     int columns;
76     size_t size;
77     char *answer;
78
79     cs = strchr(str, '\n');
80     if (cs == NULL)
81       cs = &str[strlen(str)];
82     columns = std::min(cs - str, long(console_cb_columns - console_cb_column));
83     if (columns > 0) {
84       size = fwrite(str, 1, columns, stdout);
85       assert(size == size_t(columns));
86       str += columns;
87       console_cb_column += columns;
88       continue;
89     }
90     if (*str == '\n')
91       str++;
92     else assert(console_cb_column < console_cb_columns);
93     putchar('\n');
94     console_cb_row++;
95     console_cb_column = 0;
96     if (console_cb_row < console_cb_rows - 1)
97       continue;
98     answer = llgdb_readline("---Type <return> to continue, or q <return> to quit---");
99     for (cs = answer; isspace(*cs); cs++)
100       ;
101     if (*cs == 'q') {
102       free(answer);
103       puts("Quit");
104       console_cb_drop = true;
105       return;
106     }
107     free(answer);
108     console_cb_row = 0;
109   }
110 }
111 #endif
112
113 static void history_save(int rc, void *arg) {
114   const char *history_filename = static_cast<const char *>(arg);
115   write_history(history_filename);
116 }
117
118 static std::string settings_show_raw(SBDebugger &dbg, const char *setting, const char *type) {
119   SBCommandReturnObject Result;
120   dbg.GetCommandInterpreter().HandleCommand(("settings show " + std::string(setting)).c_str(), Result, /*add_to_history*/ false);
121   assert(Result.Succeeded());
122   // <plugin.process.gdb-remote.packet-timeout (unsigned) = 1\n
123   std::string str = Result.GetOutput();
124   const std::string expect = (boost::format("%1% (%2%) = ") % setting % type).str();
125   if (str.compare(0,expect.length(),expect)!=0||str[str.length()-1]!='\n')
126     error(1,0,"Unexpected 'settings show' response: expected=\"%s[...]\\n\" got=\"%s\"",expect.c_str(),str.c_str());
127   return str.substr(expect.length(),str.length()-1-expect.length());
128 }
129
130 #if 0
131 static int settings_show_int(SBDebugger &dbg, const char *setting) {
132   std::string string = settings_show_raw(dbg, setting,"unsigned");
133   long l;
134   int retval;
135   char *end;
136
137   errno = 0;
138   retval = l = strtol(string.c_str(), &end, 10);
139   assert(errno == 0 && (end == NULL || *end == '\0') && retval == l);
140   return retval;
141 }
142
143 static bool settings_show_bool(SBDebugger &dbg, const char *setting) {
144   std::string string = settings_show_raw(dbg, setting,"unsigned");
145   bool retval;
146
147   retval = (string == "on");
148   assert(retval || string == "off");
149   return retval;
150 }
151 #endif
152
153 static std::string settings_show_string(SBDebugger &dbg, const char *setting) {
154   std::string val = settings_show_raw(dbg,setting,"string");
155   if (val.length()<2 || val[0] != '"' || val[val.length()-1]!='"')
156     error(1,0,"Unexpected 'settings show' string response: expected '\"' quoting: %s", val.c_str());
157   return val.substr(1,val.length()-2);
158 }
159
160 static SBCommandReturnObject subcommand(SBDebugger &dbg, const char *cmd, bool print_command=true, bool add_to_history=false) {
161   if (print_command)
162     printf("%s%s\n",settings_show_string(dbg, "prompt").c_str(),cmd);
163   SBCommandReturnObject Result;
164   dbg.GetCommandInterpreter().HandleCommand(cmd, Result, add_to_history);
165   Result.PutOutput(stdout);
166   Result.PutError(stdout);
167   return Result;
168 }
169
170 static void default_command(SBDebugger &dbg, const char *cmd) {
171   subcommand(dbg, cmd, false/*print_command*/, true/*add_to_history*/);
172 }
173
174 static void quit_command(SBDebugger &dbg, const char *cmd) {
175   exit(EXIT_SUCCESS);
176 }
177
178 static void start_command(SBDebugger &dbg, const char *cmd) {
179   if (!subcommand(dbg, "b main").Succeeded())
180     return;
181   subcommand(dbg, ("run "+std::string(cmd)).c_str());
182 }
183
184 static void commands_command(SBDebugger &dbg, const char *cmd_param) {
185 #if 0
186   char *cmd = xstrdup("");
187   size_t cmd_len = 0;
188   mi_output *res;
189   char *bpnum = NULL;
190   int nesting;
191
192   if (*cmd_param == '\0') {
193     mi_set_console_cb(h, commands_command_console_cb, &bpnum);
194     dbg.HandleCommand(h, "output $bpnum");
195     mi_set_console_cb(h, console_cb, NULL);
196     assert(bpnum != NULL);
197     cmd_param = bpnum;
198   }
199
200   printf(_("Type commands for breakpoint(s) %s, one per line.\n"
201            "End with a line saying just \"end\".\n"),
202          cmd_param);
203
204   nesting = 0;
205   while (nesting >= 0) {
206     char *prompt, *data, *data2, *start, *end;
207     size_t data2_len;
208     int do_nest = 0;
209
210     prompt = xstrprintf("%*s>", (int)nesting);
211     data = llgdb_readline(prompt);
212     free(prompt);
213     if (data == NULL)
214       data = xstrdup("end");
215
216     for (start = data; isspace(*start); start++)
217       ;
218     for (end = data + strlen(data); end > start && isspace(end[-1]); end--)
219       ;
220     data2 = malloc(end - start + 1);
221     memcpy(data2, start, end - start);
222     data2[end - start] = '\0';
223     if (strcmp(data2, "python") == 0)
224       do_nest = 1;
225     if (strcmp(data2, "end") == 0)
226       do_nest = -1;
227     for (end = data2; *end && !isspace(*end); end++)
228       ;
229     *end = '\0';
230
231     /* Here is a bug in GDB, it does not recognize command shortcuts.  */
232     if (strcmp(data2, "while") == 0 || strcmp(data2, "if") == 0 ||
233         strcmp(data2, "commands") == 0 ||
234         strcmp(data2, "while-stepping") == 0 ||
235         strcmp(data2, "stepping") == 0 || strcmp(data2, "ws") == 0)
236       do_nest = 1;
237     free(data2);
238
239     nesting += do_nest;
240     if (nesting < 0)
241       break;
242
243     data2 = mi_escape(data);
244     free(data);
245     data2_len = strlen(data2);
246     cmd = xrealloc(cmd, cmd_len + 2 + data2_len + 2);
247     cmd[cmd_len] = ' ';
248     cmd[cmd_len + 1] = '"';
249     memcpy(&cmd[cmd_len + 2], data2, data2_len);
250     cmd[cmd_len + 2 + data2_len] = '"';
251     cmd[cmd_len + 2 + data2_len + 1] = '\0';
252     cmd_len += 2 + data2_len + 1;
253     free(data2);
254   }
255
256   mi_send(h, "-break-commands %s%s\n", cmd_param, cmd);
257   free(cmd);
258   free(bpnum);
259
260   res = mi_get_response_blk(h);
261   assert(res != NULL && res->next == NULL && res->tclass == MI_CL_DONE &&
262              res->c == NULL);
263   mi_free_output(res);
264 #endif
265 }
266
267 struct Cmd {
268   const char *name;
269   int min_shortcut;
270   typedef void (func_t)(SBDebugger &dbg, const char *cmd);
271   func_t *func;
272   Cmd(const char *name_, int min_shortcut_, func_t *func_):name(name_),min_shortcut(min_shortcut_),func(func_) {}
273 };
274 const Cmd cmds[] = {
275   Cmd("quit", 1, quit_command),
276   Cmd("start", 5, start_command),
277   Cmd("commands", 4, commands_command),
278 };
279
280 static void executecommand(SBDebugger &dbg, const char *cmd_s) {
281   const char *start, *end, *cs;
282
283   cs = cmd_s;
284   while (isspace(*cs))
285     cs++;
286   start = cs;
287   while (isalnum(*cs))
288     cs++;
289   end = cs;
290   while (isspace(*cs))
291     cs++;
292
293   for (const Cmd &cmd : cmds)
294     if (cmd.min_shortcut <= end - start &&
295         size_t(end - start) <= strlen(cmd.name) &&
296         strncmp(start, cmd.name, end - start) == 0)
297       return cmd.func(dbg, cs);
298
299   return default_command(dbg, cmd_s);
300 }
301
302 static char *xstrdup(const char *cs) {
303   char *s = static_cast<char *>(malloc(strlen(cs)+1));
304   assert(s);
305   strcpy(s,cs);
306   return s;
307 }
308
309 static SBDebugger *completion_entry_function_dbgp;
310 static char *completion_entry_function(const char *text, int matches) {
311   SBDebugger &dbg = *completion_entry_function_dbgp;
312   static std::vector<std::string> found_vec;
313   assert(matches >= 0);
314   if (matches == 0) {
315     found_vec.clear();
316     SBStringList matches;
317     // LLDB: FIXME: These two values are not implemented. m_match_start_point; m_max_return_elements;
318     const int match_start_point = 0;
319     // LLDB: Only max_return_elements == -1 is supported at present:
320     const int max_return_elements = -1;
321     int got=dbg.GetCommandInterpreter().HandleCompletion(text, strlen(text)/*cursor_pos*/, match_start_point, max_return_elements, matches);
322     assert(matches.IsValid());
323     if (unsigned(got+1)!=matches.GetSize())
324       error(1,0,"HandleCompletion=%d +1 != %u=matches.GetSize()",got,matches.GetSize());
325     //for (size_t ix=0;ix<matches.GetSize();++ix)
326     //  fprintf(stderr,"match[%zu]=<%s>\n",ix,matches.GetStringAtIndex(ix));
327     const char *first=matches.GetStringAtIndex(0);
328     assert(matches.GetSize()>=1);
329     if (first[0]) {
330       std::string str(first);
331       while (!str.empty()&&str.back()==' ')
332         str.pop_back();
333       found_vec.push_back(text + str);
334     } else
335       for (size_t ix=1;ix<matches.GetSize();++ix)
336         found_vec.push_back(matches.GetStringAtIndex(ix));
337   }
338   if (size_t(matches) < found_vec.size())
339     return xstrdup(found_vec[matches].c_str());
340   else if (size_t(matches) == found_vec.size())
341     return nullptr;
342   else
343     assert(0);
344 }
345
346 static std::deque<std::string> opt_iex, opt_ex;
347 static std::optional<std::string> opt_exec, opt_core;
348 static std::vector<std::string> opt_args;
349
350 std::string string_quote(std::string &&str) {
351   std::string retval = "\"";
352   for (const char c : str) {
353     if (c != '"')
354       retval.push_back(c);
355     else
356       retval += "\\\"";
357   }
358   return retval + "\"";
359 }
360
361 std::optional<std::string> next_arg() {
362   if (!opt_iex.empty()) {
363     std::string retval = opt_iex.front();
364     opt_iex.pop_front();
365     return retval;
366   }
367   if (opt_exec || opt_core) {
368     std::string cmd = "target create";
369     if (opt_exec) {
370       cmd += " " + string_quote(std::move(*opt_exec));
371       opt_exec.reset();
372     }
373     if (opt_core) {
374       cmd += " --core " + string_quote(std::move(*opt_core));
375       opt_core.reset();
376     }
377     return cmd;
378   }
379   if (!opt_ex.empty()) {
380     std::string retval = opt_ex.front();
381     opt_ex.pop_front();
382     return retval;
383   }
384   return {};
385 }
386
387 int main(int argc, char **argv) {
388   llvm::StringRef ToolName = argv[0];
389   llvm::sys::PrintStackTraceOnErrorSignal(ToolName);
390   llvm::PrettyStackTraceProgram X(argc, argv);
391
392   for (int argi = 1; argi < argc; ++argi) {
393     std::string arg = argv[argi];
394     if (arg == "-iex") {
395       const char *cmd = argv[++argi];
396       assert(cmd);
397       opt_iex.push_back(cmd);
398     } else if (arg == "-ex") {
399       const char *cmd = argv[++argi];
400       assert(cmd);
401       opt_ex.push_back(cmd);
402     } else if (arg == "--args") {
403       const char *exec = argv[++argi];
404       assert(exec);
405       assert(!opt_exec);
406       assert(!opt_core);
407       assert(opt_args.empty());
408       opt_exec=std::string(exec);
409       while (++argi<argc) {
410         arg = argv[argi];
411         opt_args.push_back(std::move(arg));
412       }
413     } else if (!opt_exec)
414       opt_exec=std::move(arg);
415     else if (!opt_core)
416       opt_core=std::move(arg);
417     else
418       error(1,0,"Excessive argument: %s",arg.c_str());
419   }
420
421   SBDebugger::Initialize();
422   SBDebugger dbg = SBDebugger::Create();
423   dbg.HandleCommand("settings set symbols.enable-external-lookup false");
424
425   completion_entry_function_dbgp=&dbg;
426   rl_completion_entry_function = completion_entry_function;
427   rl_readline_name = "llgdb";
428
429   if (true/*settings_show_bool(dbg, "history save")*/) {
430     int history_size = 1000000; //settings_show_int(dbg, "history size");
431     const char *HOME = getenv("HOME");
432     assert(HOME);
433     static const std::string history_filename = std::string(HOME) + "/.llgdb_history"; // settings_show_string(dbg, "history filename");
434     //gdb_set_bool(dbg, "history save", false);
435     stifle_history(history_size);
436     read_history(history_filename.c_str());
437     on_exit(history_save, static_cast<void *>(const_cast<char *>(history_filename.c_str())));
438     /* Do not free HISTORY_FILENAME.  */
439   }
440
441   for (;;) {
442
443     std::string prompt = settings_show_string(dbg, "prompt");
444     std::optional<std::string> arg = next_arg();
445     std::string cmd;
446     if (!arg) {
447       char *cmd_s = llgdb_readline(prompt.c_str());
448       if (cmd_s)
449         add_history(cmd_s);
450       cmd = cmd_s?:"quit";
451       free(cmd_s);
452     } else {
453       puts((prompt + *arg).c_str());
454       cmd = std::move(*arg);
455     }
456
457 #if 0
458     /* FIXME: -ex commands do not have pagination set.  */
459     if (false /*!settings_show_bool(h, "pagination")*/)
460       console_cb_rows = 0;
461     else
462       rl_get_screen_size(&console_cb_rows, &console_cb_columns);
463     console_cb_drop = false;
464     console_cb_row = console_cb_column = 0;
465 #endif
466
467     executecommand(dbg, cmd.c_str());
468   }
469 }