gdb: bool-ify follow_fork
[deliverable/binutils-gdb.git] / gdb / break-catch-syscall.c
CommitLineData
10304ef3
SDJ
1/* Everything about syscall catchpoints, for GDB.
2
b811d2c2 3 Copyright (C) 2009-2020 Free Software Foundation, Inc.
10304ef3
SDJ
4
5 This file is part of GDB.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20#include "defs.h"
21#include <ctype.h>
22#include "breakpoint.h"
23#include "gdbcmd.h"
24#include "inferior.h"
4de283e4
TT
25#include "cli/cli-utils.h"
26#include "annotate.h"
10304ef3 27#include "mi/mi-common.h"
d55e5aa6 28#include "valprint.h"
4de283e4
TT
29#include "arch-utils.h"
30#include "observable.h"
10304ef3 31#include "xml-syscall.h"
7f6aba03 32#include "cli/cli-style.h"
10304ef3
SDJ
33
34/* An instance of this type is used to represent a syscall catchpoint.
c1fc2657 35 A breakpoint is really of this type iff its ops pointer points to
10304ef3
SDJ
36 CATCH_SYSCALL_BREAKPOINT_OPS. */
37
c1fc2657 38struct syscall_catchpoint : public breakpoint
10304ef3 39{
10304ef3 40 /* Syscall numbers used for the 'catch syscall' feature. If no
e12c9b7a
TT
41 syscall has been specified for filtering, it is empty.
42 Otherwise, it holds a list of all syscalls to be caught. */
43 std::vector<int> syscalls_to_be_caught;
10304ef3
SDJ
44};
45
10304ef3
SDJ
46struct catch_syscall_inferior_data
47{
48 /* We keep a count of the number of times the user has requested a
49 particular syscall to be tracked, and pass this information to the
50 target. This lets capable targets implement filtering directly. */
51
52 /* Number of times that "any" syscall is requested. */
53 int any_syscall_count;
54
55 /* Count of each system call. */
b6f48cb0 56 std::vector<int> syscalls_counts;
10304ef3
SDJ
57
58 /* This counts all syscall catch requests, so we can readily determine
59 if any catching is necessary. */
60 int total_syscalls_count;
61};
62
6ae614f6
TT
63static const struct inferior_key<struct catch_syscall_inferior_data>
64 catch_syscall_inferior_data;
65
b6f48cb0 66static struct catch_syscall_inferior_data *
10304ef3
SDJ
67get_catch_syscall_inferior_data (struct inferior *inf)
68{
69 struct catch_syscall_inferior_data *inf_data;
70
6ae614f6 71 inf_data = catch_syscall_inferior_data.get (inf);
10304ef3 72 if (inf_data == NULL)
6ae614f6 73 inf_data = catch_syscall_inferior_data.emplace (inf);
10304ef3
SDJ
74
75 return inf_data;
76}
77
10304ef3
SDJ
78/* Implement the "insert" breakpoint_ops method for syscall
79 catchpoints. */
80
81static int
82insert_catch_syscall (struct bp_location *bl)
83{
84 struct syscall_catchpoint *c = (struct syscall_catchpoint *) bl->owner;
85 struct inferior *inf = current_inferior ();
86 struct catch_syscall_inferior_data *inf_data
87 = get_catch_syscall_inferior_data (inf);
88
89 ++inf_data->total_syscalls_count;
e12c9b7a 90 if (c->syscalls_to_be_caught.empty ())
10304ef3
SDJ
91 ++inf_data->any_syscall_count;
92 else
93 {
e12c9b7a 94 for (int iter : c->syscalls_to_be_caught)
10304ef3 95 {
b6f48cb0
TT
96 if (iter >= inf_data->syscalls_counts.size ())
97 inf_data->syscalls_counts.resize (iter + 1);
98 ++inf_data->syscalls_counts[iter];
10304ef3
SDJ
99 }
100 }
101
e99b03dc 102 return target_set_syscall_catchpoint (inferior_ptid.pid (),
10304ef3
SDJ
103 inf_data->total_syscalls_count != 0,
104 inf_data->any_syscall_count,
649a140c 105 inf_data->syscalls_counts);
10304ef3
SDJ
106}
107
108/* Implement the "remove" breakpoint_ops method for syscall
109 catchpoints. */
110
111static int
73971819 112remove_catch_syscall (struct bp_location *bl, enum remove_bp_reason reason)
10304ef3
SDJ
113{
114 struct syscall_catchpoint *c = (struct syscall_catchpoint *) bl->owner;
115 struct inferior *inf = current_inferior ();
116 struct catch_syscall_inferior_data *inf_data
117 = get_catch_syscall_inferior_data (inf);
118
119 --inf_data->total_syscalls_count;
e12c9b7a 120 if (c->syscalls_to_be_caught.empty ())
10304ef3
SDJ
121 --inf_data->any_syscall_count;
122 else
123 {
e12c9b7a 124 for (int iter : c->syscalls_to_be_caught)
10304ef3 125 {
b6f48cb0 126 if (iter >= inf_data->syscalls_counts.size ())
10304ef3
SDJ
127 /* Shouldn't happen. */
128 continue;
b6f48cb0 129 --inf_data->syscalls_counts[iter];
10304ef3
SDJ
130 }
131 }
132
e99b03dc 133 return target_set_syscall_catchpoint (inferior_ptid.pid (),
10304ef3
SDJ
134 inf_data->total_syscalls_count != 0,
135 inf_data->any_syscall_count,
649a140c 136 inf_data->syscalls_counts);
10304ef3
SDJ
137}
138
139/* Implement the "breakpoint_hit" breakpoint_ops method for syscall
140 catchpoints. */
141
142static int
143breakpoint_hit_catch_syscall (const struct bp_location *bl,
bd522513 144 const address_space *aspace, CORE_ADDR bp_addr,
10304ef3
SDJ
145 const struct target_waitstatus *ws)
146{
147 /* We must check if we are catching specific syscalls in this
148 breakpoint. If we are, then we must guarantee that the called
149 syscall is the same syscall we are catching. */
150 int syscall_number = 0;
151 const struct syscall_catchpoint *c
152 = (const struct syscall_catchpoint *) bl->owner;
153
154 if (ws->kind != TARGET_WAITKIND_SYSCALL_ENTRY
155 && ws->kind != TARGET_WAITKIND_SYSCALL_RETURN)
156 return 0;
157
158 syscall_number = ws->value.syscall_number;
159
160 /* Now, checking if the syscall is the same. */
e12c9b7a 161 if (!c->syscalls_to_be_caught.empty ())
10304ef3 162 {
e12c9b7a 163 for (int iter : c->syscalls_to_be_caught)
10304ef3
SDJ
164 if (syscall_number == iter)
165 return 1;
166
167 return 0;
168 }
169
170 return 1;
171}
172
173/* Implement the "print_it" breakpoint_ops method for syscall
174 catchpoints. */
175
176static enum print_stop_action
177print_it_catch_syscall (bpstat bs)
178{
179 struct ui_out *uiout = current_uiout;
180 struct breakpoint *b = bs->breakpoint_at;
181 /* These are needed because we want to know in which state a
182 syscall is. It can be in the TARGET_WAITKIND_SYSCALL_ENTRY
183 or TARGET_WAITKIND_SYSCALL_RETURN, and depending on it we
184 must print "called syscall" or "returned from syscall". */
10304ef3
SDJ
185 struct target_waitstatus last;
186 struct syscall s;
187 struct gdbarch *gdbarch = bs->bp_location_at->gdbarch;
188
5b6d1e4f 189 get_last_target_status (nullptr, nullptr, &last);
10304ef3
SDJ
190
191 get_syscall_by_number (gdbarch, last.value.syscall_number, &s);
192
193 annotate_catchpoint (b->number);
f303dbd6 194 maybe_print_thread_hit_breakpoint (uiout);
10304ef3
SDJ
195
196 if (b->disposition == disp_del)
112e8700 197 uiout->text ("Temporary catchpoint ");
10304ef3 198 else
112e8700
SM
199 uiout->text ("Catchpoint ");
200 if (uiout->is_mi_like_p ())
10304ef3 201 {
112e8700 202 uiout->field_string ("reason",
10304ef3
SDJ
203 async_reason_lookup (last.kind == TARGET_WAITKIND_SYSCALL_ENTRY
204 ? EXEC_ASYNC_SYSCALL_ENTRY
205 : EXEC_ASYNC_SYSCALL_RETURN));
112e8700 206 uiout->field_string ("disp", bpdisp_text (b->disposition));
10304ef3 207 }
381befee 208 uiout->field_signed ("bkptno", b->number);
10304ef3
SDJ
209
210 if (last.kind == TARGET_WAITKIND_SYSCALL_ENTRY)
112e8700 211 uiout->text (" (call to syscall ");
10304ef3 212 else
112e8700 213 uiout->text (" (returned from syscall ");
10304ef3 214
112e8700 215 if (s.name == NULL || uiout->is_mi_like_p ())
381befee 216 uiout->field_signed ("syscall-number", last.value.syscall_number);
10304ef3 217 if (s.name != NULL)
112e8700 218 uiout->field_string ("syscall-name", s.name);
10304ef3 219
112e8700 220 uiout->text ("), ");
10304ef3
SDJ
221
222 return PRINT_SRC_AND_LOC;
223}
224
225/* Implement the "print_one" breakpoint_ops method for syscall
226 catchpoints. */
227
228static void
229print_one_catch_syscall (struct breakpoint *b,
230 struct bp_location **last_loc)
231{
232 struct syscall_catchpoint *c = (struct syscall_catchpoint *) b;
233 struct value_print_options opts;
234 struct ui_out *uiout = current_uiout;
235 struct gdbarch *gdbarch = b->loc->gdbarch;
236
237 get_user_print_options (&opts);
238 /* Field 4, the address, is omitted (which makes the columns not
239 line up too nicely with the headers, but the effect is relatively
240 readable). */
241 if (opts.addressprint)
112e8700 242 uiout->field_skip ("addr");
10304ef3
SDJ
243 annotate_field (5);
244
e12c9b7a 245 if (c->syscalls_to_be_caught.size () > 1)
112e8700 246 uiout->text ("syscalls \"");
10304ef3 247 else
112e8700 248 uiout->text ("syscall \"");
10304ef3 249
e12c9b7a 250 if (!c->syscalls_to_be_caught.empty ())
10304ef3 251 {
10304ef3
SDJ
252 char *text = xstrprintf ("%s", "");
253
e12c9b7a 254 for (int iter : c->syscalls_to_be_caught)
10304ef3 255 {
5b38f9c1 256 char *previous_text = text;
10304ef3
SDJ
257 struct syscall s;
258 get_syscall_by_number (gdbarch, iter, &s);
259
260 if (s.name != NULL)
261 text = xstrprintf ("%s%s, ", text, s.name);
262 else
263 text = xstrprintf ("%s%d, ", text, iter);
264
5b38f9c1
PW
265 /* We have to xfree previous_text because xstrprintf dynamically
266 allocates new space for text on every call. */
267 xfree (previous_text);
10304ef3
SDJ
268 }
269 /* Remove the last comma. */
270 text[strlen (text) - 2] = '\0';
112e8700 271 uiout->field_string ("what", text);
5b38f9c1
PW
272 /* xfree last text. */
273 xfree (text);
10304ef3
SDJ
274 }
275 else
7f6aba03 276 uiout->field_string ("what", "<any syscall>", metadata_style.style ());
112e8700 277 uiout->text ("\" ");
10304ef3 278
112e8700
SM
279 if (uiout->is_mi_like_p ())
280 uiout->field_string ("catch-type", "syscall");
10304ef3
SDJ
281}
282
283/* Implement the "print_mention" breakpoint_ops method for syscall
284 catchpoints. */
285
286static void
287print_mention_catch_syscall (struct breakpoint *b)
288{
289 struct syscall_catchpoint *c = (struct syscall_catchpoint *) b;
290 struct gdbarch *gdbarch = b->loc->gdbarch;
291
e12c9b7a 292 if (!c->syscalls_to_be_caught.empty ())
10304ef3 293 {
e12c9b7a 294 if (c->syscalls_to_be_caught.size () > 1)
10304ef3
SDJ
295 printf_filtered (_("Catchpoint %d (syscalls"), b->number);
296 else
297 printf_filtered (_("Catchpoint %d (syscall"), b->number);
298
e12c9b7a 299 for (int iter : c->syscalls_to_be_caught)
10304ef3
SDJ
300 {
301 struct syscall s;
302 get_syscall_by_number (gdbarch, iter, &s);
303
e12c9b7a 304 if (s.name != NULL)
10304ef3
SDJ
305 printf_filtered (" '%s' [%d]", s.name, s.number);
306 else
307 printf_filtered (" %d", s.number);
308 }
309 printf_filtered (")");
310 }
311 else
312 printf_filtered (_("Catchpoint %d (any syscall)"),
313 b->number);
314}
315
316/* Implement the "print_recreate" breakpoint_ops method for syscall
317 catchpoints. */
318
319static void
320print_recreate_catch_syscall (struct breakpoint *b, struct ui_file *fp)
321{
322 struct syscall_catchpoint *c = (struct syscall_catchpoint *) b;
323 struct gdbarch *gdbarch = b->loc->gdbarch;
324
325 fprintf_unfiltered (fp, "catch syscall");
326
e12c9b7a 327 for (int iter : c->syscalls_to_be_caught)
10304ef3 328 {
e12c9b7a 329 struct syscall s;
10304ef3 330
e12c9b7a
TT
331 get_syscall_by_number (gdbarch, iter, &s);
332 if (s.name != NULL)
333 fprintf_unfiltered (fp, " %s", s.name);
334 else
335 fprintf_unfiltered (fp, " %d", s.number);
10304ef3 336 }
e12c9b7a 337
10304ef3
SDJ
338 print_recreate_thread (b, fp);
339}
340
341/* The breakpoint_ops structure to be used in syscall catchpoints. */
342
343static struct breakpoint_ops catch_syscall_breakpoint_ops;
344
345/* Returns non-zero if 'b' is a syscall catchpoint. */
346
347static int
348syscall_catchpoint_p (struct breakpoint *b)
349{
350 return (b->ops == &catch_syscall_breakpoint_ops);
351}
352
353static void
e12c9b7a 354create_syscall_event_catchpoint (int tempflag, std::vector<int> &&filter,
10304ef3
SDJ
355 const struct breakpoint_ops *ops)
356{
10304ef3
SDJ
357 struct gdbarch *gdbarch = get_current_arch ();
358
b270e6f9
TT
359 std::unique_ptr<syscall_catchpoint> c (new syscall_catchpoint ());
360 init_catchpoint (c.get (), gdbarch, tempflag, NULL, ops);
2f5404b3 361 c->syscalls_to_be_caught = std::move (filter);
10304ef3 362
b270e6f9 363 install_breakpoint (0, std::move (c), 1);
10304ef3
SDJ
364}
365
e12c9b7a
TT
366/* Splits the argument using space as delimiter. */
367
368static std::vector<int>
eb4c3f4a 369catch_syscall_split_args (const char *arg)
10304ef3 370{
e12c9b7a 371 std::vector<int> result;
10304ef3
SDJ
372 struct gdbarch *gdbarch = target_gdbarch ();
373
374 while (*arg != '\0')
375 {
376 int i, syscall_number;
377 char *endptr;
378 char cur_name[128];
379 struct syscall s;
380
381 /* Skip whitespace. */
382 arg = skip_spaces (arg);
383
384 for (i = 0; i < 127 && arg[i] && !isspace (arg[i]); ++i)
385 cur_name[i] = arg[i];
386 cur_name[i] = '\0';
387 arg += i;
388
e3487908 389 /* Check if the user provided a syscall name, group, or a number. */
10304ef3
SDJ
390 syscall_number = (int) strtol (cur_name, &endptr, 0);
391 if (*endptr == '\0')
e3487908
GKB
392 {
393 get_syscall_by_number (gdbarch, syscall_number, &s);
e12c9b7a 394 result.push_back (s.number);
e3487908
GKB
395 }
396 else if (startswith (cur_name, "g:")
397 || startswith (cur_name, "group:"))
398 {
399 /* We have a syscall group. Let's expand it into a syscall
400 list before inserting. */
e3487908
GKB
401 const char *group_name;
402
403 /* Skip over "g:" and "group:" prefix strings. */
404 group_name = strchr (cur_name, ':') + 1;
405
4794efbf 406 if (!get_syscalls_by_group (gdbarch, group_name, &result))
e3487908 407 error (_("Unknown syscall group '%s'."), group_name);
e3487908 408 }
10304ef3
SDJ
409 else
410 {
e9076973
JB
411 /* We have a name. Let's check if it's valid and fetch a
412 list of matching numbers. */
413 if (!get_syscalls_by_name (gdbarch, cur_name, &result))
10304ef3
SDJ
414 /* Here we have to issue an error instead of a warning,
415 because GDB cannot do anything useful if there's no
416 syscall number to be caught. */
417 error (_("Unknown syscall name '%s'."), cur_name);
e3487908 418 }
10304ef3
SDJ
419 }
420
10304ef3
SDJ
421 return result;
422}
423
424/* Implement the "catch syscall" command. */
425
426static void
eb4c3f4a 427catch_syscall_command_1 (const char *arg, int from_tty,
10304ef3
SDJ
428 struct cmd_list_element *command)
429{
430 int tempflag;
e12c9b7a 431 std::vector<int> filter;
10304ef3
SDJ
432 struct syscall s;
433 struct gdbarch *gdbarch = get_current_arch ();
434
435 /* Checking if the feature if supported. */
436 if (gdbarch_get_syscall_number_p (gdbarch) == 0)
437 error (_("The feature 'catch syscall' is not supported on \
438this architecture yet."));
439
440 tempflag = get_cmd_context (command) == CATCH_TEMPORARY;
441
442 arg = skip_spaces (arg);
443
444 /* We need to do this first "dummy" translation in order
445 to get the syscall XML file loaded or, most important,
446 to display a warning to the user if there's no XML file
447 for his/her architecture. */
448 get_syscall_by_number (gdbarch, 0, &s);
449
450 /* The allowed syntax is:
451 catch syscall
452 catch syscall <name | number> [<name | number> ... <name | number>]
453
454 Let's check if there's a syscall name. */
455
456 if (arg != NULL)
457 filter = catch_syscall_split_args (arg);
10304ef3 458
e12c9b7a 459 create_syscall_event_catchpoint (tempflag, std::move (filter),
10304ef3
SDJ
460 &catch_syscall_breakpoint_ops);
461}
462
463
464/* Returns 0 if 'bp' is NOT a syscall catchpoint,
465 non-zero otherwise. */
466static int
467is_syscall_catchpoint_enabled (struct breakpoint *bp)
468{
469 if (syscall_catchpoint_p (bp)
470 && bp->enable_state != bp_disabled
471 && bp->enable_state != bp_call_disabled)
472 return 1;
473 else
474 return 0;
475}
476
477int
478catch_syscall_enabled (void)
479{
480 struct catch_syscall_inferior_data *inf_data
481 = get_catch_syscall_inferior_data (current_inferior ());
482
483 return inf_data->total_syscalls_count != 0;
484}
485
486/* Helper function for catching_syscall_number. If B is a syscall
487 catchpoint for SYSCALL_NUMBER, return 1 (which will make
488 'breakpoint_find_if' return). Otherwise, return 0. */
489
490static int
491catching_syscall_number_1 (struct breakpoint *b,
492 void *data)
493{
494 int syscall_number = (int) (uintptr_t) data;
495
496 if (is_syscall_catchpoint_enabled (b))
497 {
498 struct syscall_catchpoint *c = (struct syscall_catchpoint *) b;
499
e12c9b7a 500 if (!c->syscalls_to_be_caught.empty ())
10304ef3 501 {
e12c9b7a 502 for (int iter : c->syscalls_to_be_caught)
10304ef3
SDJ
503 if (syscall_number == iter)
504 return 1;
505 }
506 else
507 return 1;
508 }
509
510 return 0;
511}
512
513int
514catching_syscall_number (int syscall_number)
515{
516 struct breakpoint *b = breakpoint_find_if (catching_syscall_number_1,
517 (void *) (uintptr_t) syscall_number);
518
519 return b != NULL;
520}
521
522/* Complete syscall names. Used by "catch syscall". */
eb3ff9a5
PA
523
524static void
10304ef3 525catch_syscall_completer (struct cmd_list_element *cmd,
eb3ff9a5 526 completion_tracker &tracker,
10304ef3
SDJ
527 const char *text, const char *word)
528{
e3487908 529 struct gdbarch *gdbarch = get_current_arch ();
3d415c26 530 gdb::unique_xmalloc_ptr<const char *> group_list;
e3487908 531 const char *prefix;
e3487908
GKB
532
533 /* Completion considers ':' to be a word separator, so we use this to
534 verify whether the previous word was a group prefix. If so, we
535 build the completion list using group names only. */
536 for (prefix = word; prefix != text && prefix[-1] != ' '; prefix--)
537 ;
538
539 if (startswith (prefix, "g:") || startswith (prefix, "group:"))
540 {
541 /* Perform completion inside 'group:' namespace only. */
3d415c26 542 group_list.reset (get_syscall_group_names (gdbarch));
eb3ff9a5 543 if (group_list != NULL)
3d415c26 544 complete_on_enum (tracker, group_list.get (), word, word);
e3487908
GKB
545 }
546 else
547 {
548 /* Complete with both, syscall names and groups. */
3d415c26
TT
549 gdb::unique_xmalloc_ptr<const char *> syscall_list
550 (get_syscall_names (gdbarch));
551 group_list.reset (get_syscall_group_names (gdbarch));
552
553 const char **group_ptr = group_list.get ();
554
555 /* Hold on to strings while we're using them. */
556 std::vector<std::string> holders;
e3487908
GKB
557
558 /* Append "group:" prefix to syscall groups. */
9a93831c
SM
559 for (int i = 0; group_ptr[i] != NULL; i++)
560 holders.push_back (string_printf ("group:%s", group_ptr[i]));
e3487908 561
9a93831c
SM
562 for (int i = 0; group_ptr[i] != NULL; i++)
563 group_ptr[i] = holders[i].c_str ();
e3487908 564
eb3ff9a5 565 if (syscall_list != NULL)
3d415c26 566 complete_on_enum (tracker, syscall_list.get (), word, word);
eb3ff9a5 567 if (group_list != NULL)
3d415c26 568 complete_on_enum (tracker, group_ptr, word, word);
e3487908 569 }
10304ef3
SDJ
570}
571
572static void
573clear_syscall_counts (struct inferior *inf)
574{
575 struct catch_syscall_inferior_data *inf_data
576 = get_catch_syscall_inferior_data (inf);
577
578 inf_data->total_syscalls_count = 0;
579 inf_data->any_syscall_count = 0;
b6f48cb0 580 inf_data->syscalls_counts.clear ();
10304ef3
SDJ
581}
582
583static void
584initialize_syscall_catchpoint_ops (void)
585{
586 struct breakpoint_ops *ops;
587
588 initialize_breakpoint_ops ();
589
590 /* Syscall catchpoints. */
591 ops = &catch_syscall_breakpoint_ops;
592 *ops = base_breakpoint_ops;
10304ef3
SDJ
593 ops->insert_location = insert_catch_syscall;
594 ops->remove_location = remove_catch_syscall;
595 ops->breakpoint_hit = breakpoint_hit_catch_syscall;
596 ops->print_it = print_it_catch_syscall;
597 ops->print_one = print_one_catch_syscall;
598 ops->print_mention = print_mention_catch_syscall;
599 ops->print_recreate = print_recreate_catch_syscall;
600}
601
6c265988 602void _initialize_break_catch_syscall ();
10304ef3 603void
6c265988 604_initialize_break_catch_syscall ()
10304ef3
SDJ
605{
606 initialize_syscall_catchpoint_ops ();
607
76727919 608 gdb::observers::inferior_exit.attach (clear_syscall_counts);
10304ef3
SDJ
609
610 add_catch_command ("syscall", _("\
e3487908
GKB
611Catch system calls by their names, groups and/or numbers.\n\
612Arguments say which system calls to catch. If no arguments are given,\n\
613every system call will be caught. Arguments, if given, should be one\n\
614or more system call names (if your system supports that), system call\n\
615groups or system call numbers."),
10304ef3
SDJ
616 catch_syscall_command_1,
617 catch_syscall_completer,
618 CATCH_PERMANENT,
619 CATCH_TEMPORARY);
620}
This page took 0.391157 seconds and 4 git commands to generate.