[gdb/testsuite] Fix dwo path in fission-*.S
[deliverable/binutils-gdb.git] / gdb / skip.c
1 /* Skipping uninteresting files and functions while stepping.
2
3 Copyright (C) 2011-2021 Free Software Foundation, Inc.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17
18 #include "defs.h"
19 #include "skip.h"
20 #include "value.h"
21 #include "valprint.h"
22 #include "ui-out.h"
23 #include "symtab.h"
24 #include "gdbcmd.h"
25 #include "command.h"
26 #include "completer.h"
27 #include "stack.h"
28 #include "cli/cli-utils.h"
29 #include "arch-utils.h"
30 #include "linespec.h"
31 #include "objfiles.h"
32 #include "breakpoint.h" /* for get_sal_arch () */
33 #include "source.h"
34 #include "filenames.h"
35 #include "fnmatch.h"
36 #include "gdb_regex.h"
37 #include "gdbsupport/gdb_optional.h"
38 #include <list>
39 #include "cli/cli-style.h"
40
41 /* True if we want to print debug printouts related to file/function
42 skipping. */
43 static bool debug_skip = false;
44
45 class skiplist_entry
46 {
47 public:
48 /* Create a skiplist_entry object and add it to the chain. */
49 static void add_entry (bool file_is_glob,
50 std::string &&file,
51 bool function_is_regexp,
52 std::string &&function);
53
54 /* Return true if the skip entry has a file or glob-style file
55 pattern that matches FUNCTION_SAL. */
56 bool skip_file_p (const symtab_and_line &function_sal) const;
57
58 /* Return true if the skip entry has a function or function regexp
59 that matches FUNCTION_NAME. */
60 bool skip_function_p (const char *function_name) const;
61
62 /* Getters. */
63 int number () const { return m_number; };
64 bool enabled () const { return m_enabled; };
65 bool file_is_glob () const { return m_file_is_glob; }
66 const std::string &file () const { return m_file; }
67 const std::string &function () const { return m_function; }
68 bool function_is_regexp () const { return m_function_is_regexp; }
69
70 /* Setters. */
71 void enable () { m_enabled = true; };
72 void disable () { m_enabled = false; };
73
74 /* Disable copy. */
75 skiplist_entry (const skiplist_entry &) = delete;
76 void operator= (const skiplist_entry &) = delete;
77
78 private:
79 /* Key that grants access to the constructor. */
80 struct private_key {};
81 public:
82 /* Public so we can construct with container::emplace_back. Since
83 it requires a private class key, it can't be called from outside.
84 Use the add_entry static factory method to construct instead. */
85 skiplist_entry (bool file_is_glob, std::string &&file,
86 bool function_is_regexp, std::string &&function,
87 private_key);
88
89 private:
90 /* Return true if we're stopped at a file to be skipped. */
91 bool do_skip_file_p (const symtab_and_line &function_sal) const;
92
93 /* Return true if we're stopped at a globbed file to be skipped. */
94 bool do_skip_gfile_p (const symtab_and_line &function_sal) const;
95
96 private: /* data */
97 int m_number = -1;
98
99 /* True if FILE is a glob-style pattern.
100 Otherwise it is the plain file name (possibly with directories). */
101 bool m_file_is_glob;
102
103 /* The name of the file or empty if no name. */
104 std::string m_file;
105
106 /* True if FUNCTION is a regexp.
107 Otherwise it is a plain function name (possibly with arguments,
108 for C++). */
109 bool m_function_is_regexp;
110
111 /* The name of the function or empty if no name. */
112 std::string m_function;
113
114 /* If this is a function regexp, the compiled form. */
115 gdb::optional<compiled_regex> m_compiled_function_regexp;
116
117 /* Enabled/disabled state. */
118 bool m_enabled = true;
119 };
120
121 static std::list<skiplist_entry> skiplist_entries;
122 static int highest_skiplist_entry_num = 0;
123
124 skiplist_entry::skiplist_entry (bool file_is_glob,
125 std::string &&file,
126 bool function_is_regexp,
127 std::string &&function,
128 private_key)
129 : m_file_is_glob (file_is_glob),
130 m_file (std::move (file)),
131 m_function_is_regexp (function_is_regexp),
132 m_function (std::move (function))
133 {
134 gdb_assert (!m_file.empty () || !m_function.empty ());
135
136 if (m_file_is_glob)
137 gdb_assert (!m_file.empty ());
138
139 if (m_function_is_regexp)
140 {
141 gdb_assert (!m_function.empty ());
142 m_compiled_function_regexp.emplace (m_function.c_str (),
143 REG_NOSUB | REG_EXTENDED,
144 _("regexp"));
145 }
146 }
147
148 void
149 skiplist_entry::add_entry (bool file_is_glob, std::string &&file,
150 bool function_is_regexp, std::string &&function)
151 {
152 skiplist_entries.emplace_back (file_is_glob,
153 std::move (file),
154 function_is_regexp,
155 std::move (function),
156 private_key {});
157
158 /* Incremented after push_back, in case push_back throws. */
159 skiplist_entries.back ().m_number = ++highest_skiplist_entry_num;
160 }
161
162 static void
163 skip_file_command (const char *arg, int from_tty)
164 {
165 struct symtab *symtab;
166 const char *filename = NULL;
167
168 /* If no argument was given, try to default to the last
169 displayed codepoint. */
170 if (arg == NULL)
171 {
172 symtab = get_last_displayed_symtab ();
173 if (symtab == NULL)
174 error (_("No default file now."));
175
176 /* It is not a typo, symtab_to_filename_for_display would be needlessly
177 ambiguous. */
178 filename = symtab_to_fullname (symtab);
179 }
180 else
181 filename = arg;
182
183 skiplist_entry::add_entry (false, std::string (filename),
184 false, std::string ());
185
186 printf_filtered (_("File %s will be skipped when stepping.\n"), filename);
187 }
188
189 /* Create a skiplist entry for the given function NAME and add it to the
190 list. */
191
192 static void
193 skip_function (const char *name)
194 {
195 skiplist_entry::add_entry (false, std::string (), false, std::string (name));
196
197 printf_filtered (_("Function %s will be skipped when stepping.\n"), name);
198 }
199
200 static void
201 skip_function_command (const char *arg, int from_tty)
202 {
203 /* Default to the current function if no argument is given. */
204 if (arg == NULL)
205 {
206 frame_info *fi = get_selected_frame (_("No default function now."));
207 struct symbol *sym = get_frame_function (fi);
208 const char *name = NULL;
209
210 if (sym != NULL)
211 name = sym->print_name ();
212 else
213 error (_("No function found containing current program point %s."),
214 paddress (get_current_arch (), get_frame_pc (fi)));
215 skip_function (name);
216 return;
217 }
218
219 skip_function (arg);
220 }
221
222 /* Process "skip ..." that does not match "skip file" or "skip function". */
223
224 static void
225 skip_command (const char *arg, int from_tty)
226 {
227 const char *file = NULL;
228 const char *gfile = NULL;
229 const char *function = NULL;
230 const char *rfunction = NULL;
231 int i;
232
233 if (arg == NULL)
234 {
235 skip_function_command (arg, from_tty);
236 return;
237 }
238
239 gdb_argv argv (arg);
240
241 for (i = 0; argv[i] != NULL; ++i)
242 {
243 const char *p = argv[i];
244 const char *value = argv[i + 1];
245
246 if (strcmp (p, "-fi") == 0
247 || strcmp (p, "-file") == 0)
248 {
249 if (value == NULL)
250 error (_("Missing value for %s option."), p);
251 file = value;
252 ++i;
253 }
254 else if (strcmp (p, "-gfi") == 0
255 || strcmp (p, "-gfile") == 0)
256 {
257 if (value == NULL)
258 error (_("Missing value for %s option."), p);
259 gfile = value;
260 ++i;
261 }
262 else if (strcmp (p, "-fu") == 0
263 || strcmp (p, "-function") == 0)
264 {
265 if (value == NULL)
266 error (_("Missing value for %s option."), p);
267 function = value;
268 ++i;
269 }
270 else if (strcmp (p, "-rfu") == 0
271 || strcmp (p, "-rfunction") == 0)
272 {
273 if (value == NULL)
274 error (_("Missing value for %s option."), p);
275 rfunction = value;
276 ++i;
277 }
278 else if (*p == '-')
279 error (_("Invalid skip option: %s"), p);
280 else if (i == 0)
281 {
282 /* Assume the user entered "skip FUNCTION-NAME".
283 FUNCTION-NAME may be `foo (int)', and therefore we pass the
284 complete original arg to skip_function command as if the user
285 typed "skip function arg". */
286 skip_function_command (arg, from_tty);
287 return;
288 }
289 else
290 error (_("Invalid argument: %s"), p);
291 }
292
293 if (file != NULL && gfile != NULL)
294 error (_("Cannot specify both -file and -gfile."));
295
296 if (function != NULL && rfunction != NULL)
297 error (_("Cannot specify both -function and -rfunction."));
298
299 /* This shouldn't happen as "skip" by itself gets punted to
300 skip_function_command. */
301 gdb_assert (file != NULL || gfile != NULL
302 || function != NULL || rfunction != NULL);
303
304 std::string entry_file;
305 if (file != NULL)
306 entry_file = file;
307 else if (gfile != NULL)
308 entry_file = gfile;
309
310 std::string entry_function;
311 if (function != NULL)
312 entry_function = function;
313 else if (rfunction != NULL)
314 entry_function = rfunction;
315
316 skiplist_entry::add_entry (gfile != NULL, std::move (entry_file),
317 rfunction != NULL, std::move (entry_function));
318
319 /* I18N concerns drive some of the choices here (we can't piece together
320 the output too much). OTOH we want to keep this simple. Therefore the
321 only polish we add to the output is to append "(s)" to "File" or
322 "Function" if they're a glob/regexp. */
323 {
324 const char *file_to_print = file != NULL ? file : gfile;
325 const char *function_to_print = function != NULL ? function : rfunction;
326 const char *file_text = gfile != NULL ? _("File(s)") : _("File");
327 const char *lower_file_text = gfile != NULL ? _("file(s)") : _("file");
328 const char *function_text
329 = rfunction != NULL ? _("Function(s)") : _("Function");
330
331 if (function_to_print == NULL)
332 {
333 printf_filtered (_("%s %s will be skipped when stepping.\n"),
334 file_text, file_to_print);
335 }
336 else if (file_to_print == NULL)
337 {
338 printf_filtered (_("%s %s will be skipped when stepping.\n"),
339 function_text, function_to_print);
340 }
341 else
342 {
343 printf_filtered (_("%s %s in %s %s will be skipped"
344 " when stepping.\n"),
345 function_text, function_to_print,
346 lower_file_text, file_to_print);
347 }
348 }
349 }
350
351 static void
352 info_skip_command (const char *arg, int from_tty)
353 {
354 int num_printable_entries = 0;
355 struct value_print_options opts;
356
357 get_user_print_options (&opts);
358
359 /* Count the number of rows in the table and see if we need space for a
360 64-bit address anywhere. */
361 for (const skiplist_entry &e : skiplist_entries)
362 if (arg == NULL || number_is_in_list (arg, e.number ()))
363 num_printable_entries++;
364
365 if (num_printable_entries == 0)
366 {
367 if (arg == NULL)
368 current_uiout->message (_("Not skipping any files or functions.\n"));
369 else
370 current_uiout->message (
371 _("No skiplist entries found with number %s.\n"), arg);
372
373 return;
374 }
375
376 ui_out_emit_table table_emitter (current_uiout, 6, num_printable_entries,
377 "SkiplistTable");
378
379 current_uiout->table_header (5, ui_left, "number", "Num"); /* 1 */
380 current_uiout->table_header (3, ui_left, "enabled", "Enb"); /* 2 */
381 current_uiout->table_header (4, ui_right, "regexp", "Glob"); /* 3 */
382 current_uiout->table_header (20, ui_left, "file", "File"); /* 4 */
383 current_uiout->table_header (2, ui_right, "regexp", "RE"); /* 5 */
384 current_uiout->table_header (40, ui_noalign, "function", "Function"); /* 6 */
385 current_uiout->table_body ();
386
387 for (const skiplist_entry &e : skiplist_entries)
388 {
389 QUIT;
390 if (arg != NULL && !number_is_in_list (arg, e.number ()))
391 continue;
392
393 ui_out_emit_tuple tuple_emitter (current_uiout, "blklst-entry");
394 current_uiout->field_signed ("number", e.number ()); /* 1 */
395
396 if (e.enabled ())
397 current_uiout->field_string ("enabled", "y"); /* 2 */
398 else
399 current_uiout->field_string ("enabled", "n"); /* 2 */
400
401 if (e.file_is_glob ())
402 current_uiout->field_string ("regexp", "y"); /* 3 */
403 else
404 current_uiout->field_string ("regexp", "n"); /* 3 */
405
406 current_uiout->field_string ("file",
407 e.file ().empty () ? "<none>"
408 : e.file ().c_str (),
409 e.file ().empty ()
410 ? metadata_style.style ()
411 : file_name_style.style ()); /* 4 */
412 if (e.function_is_regexp ())
413 current_uiout->field_string ("regexp", "y"); /* 5 */
414 else
415 current_uiout->field_string ("regexp", "n"); /* 5 */
416
417 current_uiout->field_string ("function",
418 e.function ().empty () ? "<none>"
419 : e.function ().c_str (),
420 e.function ().empty ()
421 ? metadata_style.style ()
422 : function_name_style.style ()); /* 6 */
423
424 current_uiout->text ("\n");
425 }
426 }
427
428 static void
429 skip_enable_command (const char *arg, int from_tty)
430 {
431 bool found = false;
432
433 for (skiplist_entry &e : skiplist_entries)
434 if (arg == NULL || number_is_in_list (arg, e.number ()))
435 {
436 e.enable ();
437 found = true;
438 }
439
440 if (!found)
441 error (_("No skiplist entries found with number %s."), arg);
442 }
443
444 static void
445 skip_disable_command (const char *arg, int from_tty)
446 {
447 bool found = false;
448
449 for (skiplist_entry &e : skiplist_entries)
450 if (arg == NULL || number_is_in_list (arg, e.number ()))
451 {
452 e.disable ();
453 found = true;
454 }
455
456 if (!found)
457 error (_("No skiplist entries found with number %s."), arg);
458 }
459
460 static void
461 skip_delete_command (const char *arg, int from_tty)
462 {
463 bool found = false;
464
465 for (auto it = skiplist_entries.begin (),
466 end = skiplist_entries.end ();
467 it != end;)
468 {
469 const skiplist_entry &e = *it;
470
471 if (arg == NULL || number_is_in_list (arg, e.number ()))
472 {
473 it = skiplist_entries.erase (it);
474 found = true;
475 }
476 else
477 ++it;
478 }
479
480 if (!found)
481 error (_("No skiplist entries found with number %s."), arg);
482 }
483
484 bool
485 skiplist_entry::do_skip_file_p (const symtab_and_line &function_sal) const
486 {
487 if (debug_skip)
488 fprintf_unfiltered (gdb_stdlog,
489 "skip: checking if file %s matches non-glob %s...",
490 function_sal.symtab->filename, m_file.c_str ());
491
492 bool result;
493
494 /* Check first sole SYMTAB->FILENAME. It may not be a substring of
495 symtab_to_fullname as it may contain "./" etc. */
496 if (compare_filenames_for_search (function_sal.symtab->filename,
497 m_file.c_str ()))
498 result = true;
499
500 /* Before we invoke realpath, which can get expensive when many
501 files are involved, do a quick comparison of the basenames. */
502 else if (!basenames_may_differ
503 && filename_cmp (lbasename (function_sal.symtab->filename),
504 lbasename (m_file.c_str ())) != 0)
505 result = false;
506 else
507 {
508 /* Note: symtab_to_fullname caches its result, thus we don't have to. */
509 const char *fullname = symtab_to_fullname (function_sal.symtab);
510
511 result = compare_filenames_for_search (fullname, m_file.c_str ());
512 }
513
514 if (debug_skip)
515 fprintf_unfiltered (gdb_stdlog, result ? "yes.\n" : "no.\n");
516
517 return result;
518 }
519
520 bool
521 skiplist_entry::do_skip_gfile_p (const symtab_and_line &function_sal) const
522 {
523 if (debug_skip)
524 fprintf_unfiltered (gdb_stdlog,
525 "skip: checking if file %s matches glob %s...",
526 function_sal.symtab->filename, m_file.c_str ());
527
528 bool result;
529
530 /* Check first sole SYMTAB->FILENAME. It may not be a substring of
531 symtab_to_fullname as it may contain "./" etc. */
532 if (gdb_filename_fnmatch (m_file.c_str (), function_sal.symtab->filename,
533 FNM_FILE_NAME | FNM_NOESCAPE) == 0)
534 result = true;
535
536 /* Before we invoke symtab_to_fullname, which is expensive, do a quick
537 comparison of the basenames.
538 Note that we assume that lbasename works with glob-style patterns.
539 If the basename of the glob pattern is something like "*.c" then this
540 isn't much of a win. Oh well. */
541 else if (!basenames_may_differ
542 && gdb_filename_fnmatch (lbasename (m_file.c_str ()),
543 lbasename (function_sal.symtab->filename),
544 FNM_FILE_NAME | FNM_NOESCAPE) != 0)
545 result = false;
546 else
547 {
548 /* Note: symtab_to_fullname caches its result, thus we don't have to. */
549 const char *fullname = symtab_to_fullname (function_sal.symtab);
550
551 result = compare_glob_filenames_for_search (fullname, m_file.c_str ());
552 }
553
554 if (debug_skip)
555 fprintf_unfiltered (gdb_stdlog, result ? "yes.\n" : "no.\n");
556
557 return result;
558 }
559
560 bool
561 skiplist_entry::skip_file_p (const symtab_and_line &function_sal) const
562 {
563 if (m_file.empty ())
564 return false;
565
566 if (function_sal.symtab == NULL)
567 return false;
568
569 if (m_file_is_glob)
570 return do_skip_gfile_p (function_sal);
571 else
572 return do_skip_file_p (function_sal);
573 }
574
575 bool
576 skiplist_entry::skip_function_p (const char *function_name) const
577 {
578 if (m_function.empty ())
579 return false;
580
581 bool result;
582
583 if (m_function_is_regexp)
584 {
585 if (debug_skip)
586 fprintf_unfiltered (gdb_stdlog,
587 "skip: checking if function %s matches regex %s...",
588 function_name, m_function.c_str ());
589
590 gdb_assert (m_compiled_function_regexp);
591 result
592 = (m_compiled_function_regexp->exec (function_name, 0, NULL, 0) == 0);
593 }
594 else
595 {
596 if (debug_skip)
597 fprintf_unfiltered (gdb_stdlog,
598 ("skip: checking if function %s matches non-regex "
599 "%s..."),
600 function_name, m_function.c_str ());
601 result = (strcmp_iw (function_name, m_function.c_str ()) == 0);
602 }
603
604 if (debug_skip)
605 fprintf_unfiltered (gdb_stdlog, result ? "yes.\n" : "no.\n");
606
607 return result;
608 }
609
610 /* See skip.h. */
611
612 bool
613 function_name_is_marked_for_skip (const char *function_name,
614 const symtab_and_line &function_sal)
615 {
616 if (function_name == NULL)
617 return false;
618
619 for (const skiplist_entry &e : skiplist_entries)
620 {
621 if (!e.enabled ())
622 continue;
623
624 bool skip_by_file = e.skip_file_p (function_sal);
625 bool skip_by_function = e.skip_function_p (function_name);
626
627 /* If both file and function must match, make sure we don't errantly
628 exit if only one of them match. */
629 if (!e.file ().empty () && !e.function ().empty ())
630 {
631 if (skip_by_file && skip_by_function)
632 return true;
633 }
634 /* Only one of file/function is specified. */
635 else if (skip_by_file || skip_by_function)
636 return true;
637 }
638
639 return false;
640 }
641
642 /* Completer for skip numbers. */
643
644 static void
645 complete_skip_number (cmd_list_element *cmd,
646 completion_tracker &completer,
647 const char *text, const char *word)
648 {
649 size_t word_len = strlen (word);
650
651 for (const skiplist_entry &entry : skiplist_entries)
652 {
653 gdb::unique_xmalloc_ptr<char> name (xstrprintf ("%d", entry.number ()));
654 if (strncmp (word, name.get (), word_len) == 0)
655 completer.add_completion (std::move (name));
656 }
657 }
658
659 void _initialize_step_skip ();
660 void
661 _initialize_step_skip ()
662 {
663 static struct cmd_list_element *skiplist = NULL;
664 struct cmd_list_element *c;
665
666 add_prefix_cmd ("skip", class_breakpoint, skip_command, _("\
667 Ignore a function while stepping.\n\
668 \n\
669 Usage: skip [FUNCTION-NAME]\n\
670 skip [FILE-SPEC] [FUNCTION-SPEC]\n\
671 If no arguments are given, ignore the current function.\n\
672 \n\
673 FILE-SPEC is one of:\n\
674 -fi|-file FILE-NAME\n\
675 -gfi|-gfile GLOB-FILE-PATTERN\n\
676 FUNCTION-SPEC is one of:\n\
677 -fu|-function FUNCTION-NAME\n\
678 -rfu|-rfunction FUNCTION-NAME-REGULAR-EXPRESSION"),
679 &skiplist, 1, &cmdlist);
680
681 c = add_cmd ("file", class_breakpoint, skip_file_command, _("\
682 Ignore a file while stepping.\n\
683 Usage: skip file [FILE-NAME]\n\
684 If no filename is given, ignore the current file."),
685 &skiplist);
686 set_cmd_completer (c, filename_completer);
687
688 c = add_cmd ("function", class_breakpoint, skip_function_command, _("\
689 Ignore a function while stepping.\n\
690 Usage: skip function [FUNCTION-NAME]\n\
691 If no function name is given, skip the current function."),
692 &skiplist);
693 set_cmd_completer (c, location_completer);
694
695 c = add_cmd ("enable", class_breakpoint, skip_enable_command, _("\
696 Enable skip entries.\n\
697 Usage: skip enable [NUMBER | RANGE]...\n\
698 You can specify numbers (e.g. \"skip enable 1 3\"),\n\
699 ranges (e.g. \"skip enable 4-8\"), or both (e.g. \"skip enable 1 3 4-8\").\n\n\
700 If you don't specify any numbers or ranges, we'll enable all skip entries."),
701 &skiplist);
702 set_cmd_completer (c, complete_skip_number);
703
704 c = add_cmd ("disable", class_breakpoint, skip_disable_command, _("\
705 Disable skip entries.\n\
706 Usage: skip disable [NUMBER | RANGE]...\n\
707 You can specify numbers (e.g. \"skip disable 1 3\"),\n\
708 ranges (e.g. \"skip disable 4-8\"), or both (e.g. \"skip disable 1 3 4-8\").\n\n\
709 If you don't specify any numbers or ranges, we'll disable all skip entries."),
710 &skiplist);
711 set_cmd_completer (c, complete_skip_number);
712
713 c = add_cmd ("delete", class_breakpoint, skip_delete_command, _("\
714 Delete skip entries.\n\
715 Usage: skip delete [NUMBER | RANGES]...\n\
716 You can specify numbers (e.g. \"skip delete 1 3\"),\n\
717 ranges (e.g. \"skip delete 4-8\"), or both (e.g. \"skip delete 1 3 4-8\").\n\n\
718 If you don't specify any numbers or ranges, we'll delete all skip entries."),
719 &skiplist);
720 set_cmd_completer (c, complete_skip_number);
721
722 add_info ("skip", info_skip_command, _("\
723 Display the status of skips.\n\
724 Usage: info skip [NUMBER | RANGES]...\n\
725 You can specify numbers (e.g. \"info skip 1 3\"), \n\
726 ranges (e.g. \"info skip 4-8\"), or both (e.g. \"info skip 1 3 4-8\").\n\n\
727 If you don't specify any numbers or ranges, we'll show all skips."));
728 set_cmd_completer (c, complete_skip_number);
729
730 add_setshow_boolean_cmd ("skip", class_maintenance,
731 &debug_skip, _("\
732 Set whether to print the debug output about skipping files and functions."),
733 _("\
734 Show whether the debug output about skipping files and functions is printed."),
735 _("\
736 When non-zero, debug output about skipping files and functions is displayed."),
737 NULL, NULL,
738 &setdebuglist, &showdebuglist);
739 }
This page took 0.04556 seconds and 4 git commands to generate.