1 /* Process record and replay target for GDB, the GNU debugger. 2 3 Copyright (C) 2008-2023 Free Software Foundation, Inc. 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 "gdbcmd.h" 22 #include "completer.h" 23 #include "record.h" 24 #include "observable.h" 25 #include "inferior.h" 26 #include "gdbsupport/common-utils.h" 27 #include "cli/cli-utils.h" 28 #include "disasm.h" 29 30 #include <ctype.h> 31 32 /* This is the debug switch for process record. */ 33 unsigned int record_debug = 0; 34 35 /* The number of instructions to print in "record instruction-history". */ 36 static unsigned int record_insn_history_size = 10; 37 38 /* The variable registered as control variable in the "record 39 instruction-history" command. Necessary for extra input 40 validation. */ 41 static unsigned int record_insn_history_size_setshow_var; 42 43 /* The number of functions to print in "record function-call-history". */ 44 static unsigned int record_call_history_size = 10; 45 46 /* The variable registered as control variable in the "record 47 call-history" command. Necessary for extra input validation. */ 48 static unsigned int record_call_history_size_setshow_var; 49 50 struct cmd_list_element *record_cmdlist = NULL; 51 static struct cmd_list_element *record_goto_cmdlist = NULL; 52 struct cmd_list_element *set_record_cmdlist = NULL; 53 struct cmd_list_element *show_record_cmdlist = NULL; 54 struct cmd_list_element *info_record_cmdlist = NULL; 55 56 #define DEBUG(msg, args...) \ 57 if (record_debug) \ 58 gdb_printf (gdb_stdlog, "record: " msg "\n", ##args) 59 60 /* See record.h. */ 61 62 struct target_ops * 63 find_record_target (void) 64 { 65 return find_target_at (record_stratum); 66 } 67 68 /* Check that recording is active. Throw an error, if it isn't. */ 69 70 static struct target_ops * 71 require_record_target (void) 72 { 73 struct target_ops *t; 74 75 t = find_record_target (); 76 if (t == NULL) 77 error (_("No recording is currently active.\n" 78 "Use the \"record full\" or \"record btrace\" command first.")); 79 80 return t; 81 } 82 83 /* See record.h. */ 84 85 void 86 record_preopen (void) 87 { 88 /* Check if a record target is already running. */ 89 if (find_record_target () != NULL) 90 error (_("The process is already being recorded. Use \"record stop\" to " 91 "stop recording first.")); 92 } 93 94 /* See record.h. */ 95 96 void 97 record_start (const char *method, const char *format, int from_tty) 98 { 99 if (method == NULL) 100 { 101 if (format == NULL) 102 execute_command_to_string ("record", from_tty, false); 103 else 104 error (_("Invalid format.")); 105 } 106 else if (strcmp (method, "full") == 0) 107 { 108 if (format == NULL) 109 execute_command_to_string ("record full", from_tty, false); 110 else 111 error (_("Invalid format.")); 112 } 113 else if (strcmp (method, "btrace") == 0) 114 { 115 if (format == NULL) 116 execute_command_to_string ("record btrace", from_tty, false); 117 else if (strcmp (format, "bts") == 0) 118 execute_command_to_string ("record btrace bts", from_tty, false); 119 else if (strcmp (format, "pt") == 0) 120 execute_command_to_string ("record btrace pt", from_tty, false); 121 else 122 error (_("Invalid format.")); 123 } 124 else 125 error (_("Invalid method.")); 126 } 127 128 /* See record.h. */ 129 130 void 131 record_stop (int from_tty) 132 { 133 execute_command_to_string ("record stop", from_tty, false); 134 } 135 136 /* See record.h. */ 137 138 int 139 record_read_memory (struct gdbarch *gdbarch, 140 CORE_ADDR memaddr, gdb_byte *myaddr, 141 ssize_t len) 142 { 143 int ret = target_read_memory (memaddr, myaddr, len); 144 145 if (ret != 0) 146 DEBUG ("error reading memory at addr %s len = %ld.\n", 147 paddress (gdbarch, memaddr), (long) len); 148 149 return ret; 150 } 151 152 /* Stop recording. */ 153 154 static void 155 record_stop (struct target_ops *t) 156 { 157 DEBUG ("stop %s", t->shortname ()); 158 159 t->stop_recording (); 160 } 161 162 /* Unpush the record target. */ 163 164 static void 165 record_unpush (struct target_ops *t) 166 { 167 DEBUG ("unpush %s", t->shortname ()); 168 169 current_inferior ()->unpush_target (t); 170 } 171 172 /* See record.h. */ 173 174 void 175 record_disconnect (struct target_ops *t, const char *args, int from_tty) 176 { 177 gdb_assert (t->stratum () == record_stratum); 178 179 DEBUG ("disconnect %s", t->shortname ()); 180 181 record_stop (t); 182 record_unpush (t); 183 184 target_disconnect (args, from_tty); 185 } 186 187 /* See record.h. */ 188 189 void 190 record_detach (struct target_ops *t, inferior *inf, int from_tty) 191 { 192 gdb_assert (t->stratum () == record_stratum); 193 194 DEBUG ("detach %s", t->shortname ()); 195 196 record_stop (t); 197 record_unpush (t); 198 199 target_detach (inf, from_tty); 200 } 201 202 /* See record.h. */ 203 204 void 205 record_mourn_inferior (struct target_ops *t) 206 { 207 gdb_assert (t->stratum () == record_stratum); 208 209 DEBUG ("mourn inferior %s", t->shortname ()); 210 211 /* It is safer to not stop recording. Resources will be freed when 212 threads are discarded. */ 213 record_unpush (t); 214 215 target_mourn_inferior (inferior_ptid); 216 } 217 218 /* See record.h. */ 219 220 void 221 record_kill (struct target_ops *t) 222 { 223 gdb_assert (t->stratum () == record_stratum); 224 225 DEBUG ("kill %s", t->shortname ()); 226 227 /* It is safer to not stop recording. Resources will be freed when 228 threads are discarded. */ 229 record_unpush (t); 230 231 target_kill (); 232 } 233 234 /* See record.h. */ 235 236 int 237 record_check_stopped_by_breakpoint (const address_space *aspace, 238 CORE_ADDR pc, 239 enum target_stop_reason *reason) 240 { 241 if (breakpoint_inserted_here_p (aspace, pc)) 242 { 243 if (hardware_breakpoint_inserted_here_p (aspace, pc)) 244 *reason = TARGET_STOPPED_BY_HW_BREAKPOINT; 245 else 246 *reason = TARGET_STOPPED_BY_SW_BREAKPOINT; 247 return 1; 248 } 249 250 *reason = TARGET_STOPPED_BY_NO_REASON; 251 return 0; 252 } 253 254 /* Implement "show record debug" command. */ 255 256 static void 257 show_record_debug (struct ui_file *file, int from_tty, 258 struct cmd_list_element *c, const char *value) 259 { 260 gdb_printf (file, _("Debugging of process record target is %s.\n"), 261 value); 262 } 263 264 /* Alias for "target record". */ 265 266 static void 267 cmd_record_start (const char *args, int from_tty) 268 { 269 execute_command ("target record-full", from_tty); 270 } 271 272 /* Truncate the record log from the present point 273 of replay until the end. */ 274 275 static void 276 cmd_record_delete (const char *args, int from_tty) 277 { 278 require_record_target (); 279 280 if (!target_record_is_replaying (inferior_ptid)) 281 { 282 gdb_printf (_("Already at end of record list.\n")); 283 return; 284 } 285 286 if (!target_supports_delete_record ()) 287 { 288 gdb_printf (_("The current record target does not support " 289 "this operation.\n")); 290 return; 291 } 292 293 if (!from_tty || query (_("Delete the log from this point forward " 294 "and begin to record the running message " 295 "at current PC?"))) 296 target_delete_record (); 297 } 298 299 /* Implement the "stoprecord" or "record stop" command. */ 300 301 static void 302 cmd_record_stop (const char *args, int from_tty) 303 { 304 struct target_ops *t; 305 306 t = require_record_target (); 307 308 record_stop (t); 309 record_unpush (t); 310 311 gdb_printf (_("Process record is stopped and all execution " 312 "logs are deleted.\n")); 313 314 gdb::observers::record_changed.notify (current_inferior (), 0, NULL, NULL); 315 } 316 317 318 /* The "info record" command. */ 319 320 static void 321 info_record_command (const char *args, int from_tty) 322 { 323 struct target_ops *t; 324 325 t = find_record_target (); 326 if (t == NULL) 327 { 328 gdb_printf (_("No recording is currently active.\n")); 329 return; 330 } 331 332 gdb_printf (_("Active record target: %s\n"), t->shortname ()); 333 t->info_record (); 334 } 335 336 /* The "record save" command. */ 337 338 static void 339 cmd_record_save (const char *args, int from_tty) 340 { 341 const char *recfilename; 342 char recfilename_buffer[40]; 343 344 require_record_target (); 345 346 if (args != NULL && *args != 0) 347 recfilename = args; 348 else 349 { 350 /* Default recfile name is "gdb_record.PID". */ 351 xsnprintf (recfilename_buffer, sizeof (recfilename_buffer), 352 "gdb_record.%d", inferior_ptid.pid ()); 353 recfilename = recfilename_buffer; 354 } 355 356 target_save_record (recfilename); 357 } 358 359 /* See record.h. */ 360 361 void 362 record_goto (const char *arg) 363 { 364 ULONGEST insn; 365 366 if (arg == NULL || *arg == '\0') 367 error (_("Command requires an argument (insn number to go to).")); 368 369 insn = parse_and_eval_long (arg); 370 371 require_record_target (); 372 target_goto_record (insn); 373 } 374 375 /* "record goto" command. Argument is an instruction number, 376 as given by "info record". 377 378 Rewinds the recording (forward or backward) to the given instruction. */ 379 380 static void 381 cmd_record_goto (const char *arg, int from_tty) 382 { 383 record_goto (arg); 384 } 385 386 /* The "record goto begin" command. */ 387 388 static void 389 cmd_record_goto_begin (const char *arg, int from_tty) 390 { 391 if (arg != NULL && *arg != '\0') 392 error (_("Junk after argument: %s."), arg); 393 394 require_record_target (); 395 target_goto_record_begin (); 396 } 397 398 /* The "record goto end" command. */ 399 400 static void 401 cmd_record_goto_end (const char *arg, int from_tty) 402 { 403 if (arg != NULL && *arg != '\0') 404 error (_("Junk after argument: %s."), arg); 405 406 require_record_target (); 407 target_goto_record_end (); 408 } 409 410 /* Read an instruction number from an argument string. */ 411 412 static ULONGEST 413 get_insn_number (const char **arg) 414 { 415 ULONGEST number; 416 const char *begin, *end, *pos; 417 418 begin = *arg; 419 pos = skip_spaces (begin); 420 421 if (!isdigit (*pos)) 422 error (_("Expected positive number, got: %s."), pos); 423 424 number = strtoulst (pos, &end, 10); 425 426 *arg += (end - begin); 427 428 return number; 429 } 430 431 /* Read a context size from an argument string. */ 432 433 static int 434 get_context_size (const char **arg) 435 { 436 const char *pos; 437 char *end; 438 439 pos = skip_spaces (*arg); 440 441 if (!isdigit (*pos)) 442 error (_("Expected positive number, got: %s."), pos); 443 444 long result = strtol (pos, &end, 10); 445 *arg = end; 446 return result; 447 } 448 449 /* Complain about junk at the end of an argument string. */ 450 451 static void 452 no_chunk (const char *arg) 453 { 454 if (*arg != 0) 455 error (_("Junk after argument: %s."), arg); 456 } 457 458 /* Read instruction-history modifiers from an argument string. */ 459 460 static gdb_disassembly_flags 461 get_insn_history_modifiers (const char **arg) 462 { 463 gdb_disassembly_flags modifiers; 464 const char *args; 465 466 modifiers = 0; 467 args = *arg; 468 469 if (args == NULL) 470 return modifiers; 471 472 while (*args == '/') 473 { 474 ++args; 475 476 if (*args == '\0') 477 error (_("Missing modifier.")); 478 479 for (; *args; ++args) 480 { 481 if (isspace (*args)) 482 break; 483 484 if (*args == '/') 485 continue; 486 487 switch (*args) 488 { 489 case 'm': 490 case 's': 491 modifiers |= DISASSEMBLY_SOURCE; 492 modifiers |= DISASSEMBLY_FILENAME; 493 break; 494 case 'r': 495 modifiers |= DISASSEMBLY_RAW_INSN; 496 break; 497 case 'b': 498 modifiers |= DISASSEMBLY_RAW_BYTES; 499 break; 500 case 'f': 501 modifiers |= DISASSEMBLY_OMIT_FNAME; 502 break; 503 case 'p': 504 modifiers |= DISASSEMBLY_OMIT_PC; 505 break; 506 default: 507 error (_("Invalid modifier: %c."), *args); 508 } 509 } 510 511 args = skip_spaces (args); 512 } 513 514 /* Update the argument string. */ 515 *arg = args; 516 517 return modifiers; 518 } 519 520 /* The "set record instruction-history-size / set record 521 function-call-history-size" commands are unsigned, with UINT_MAX 522 meaning unlimited. The target interfaces works with signed int 523 though, to indicate direction, so map "unlimited" to INT_MAX, which 524 is about the same as unlimited in practice. If the user does have 525 a log that huge, she can fetch it in chunks across several requests, 526 but she'll likely have other problems first... */ 527 528 static int 529 command_size_to_target_size (unsigned int size) 530 { 531 gdb_assert (size <= INT_MAX || size == UINT_MAX); 532 533 if (size == UINT_MAX) 534 return INT_MAX; 535 else 536 return size; 537 } 538 539 /* The "record instruction-history" command. */ 540 541 static void 542 cmd_record_insn_history (const char *arg, int from_tty) 543 { 544 require_record_target (); 545 546 gdb_disassembly_flags flags = get_insn_history_modifiers (&arg); 547 548 int size = command_size_to_target_size (record_insn_history_size); 549 550 if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) 551 target_insn_history (size, flags); 552 else if (strcmp (arg, "-") == 0) 553 target_insn_history (-size, flags); 554 else 555 { 556 ULONGEST begin, end; 557 558 begin = get_insn_number (&arg); 559 560 if (*arg == ',') 561 { 562 arg = skip_spaces (++arg); 563 564 if (*arg == '+') 565 { 566 arg += 1; 567 size = get_context_size (&arg); 568 569 no_chunk (arg); 570 571 target_insn_history_from (begin, size, flags); 572 } 573 else if (*arg == '-') 574 { 575 arg += 1; 576 size = get_context_size (&arg); 577 578 no_chunk (arg); 579 580 target_insn_history_from (begin, -size, flags); 581 } 582 else 583 { 584 end = get_insn_number (&arg); 585 586 no_chunk (arg); 587 588 target_insn_history_range (begin, end, flags); 589 } 590 } 591 else 592 { 593 no_chunk (arg); 594 595 target_insn_history_from (begin, size, flags); 596 } 597 598 dont_repeat (); 599 } 600 } 601 602 /* Read function-call-history modifiers from an argument string. */ 603 604 static record_print_flags 605 get_call_history_modifiers (const char **arg) 606 { 607 record_print_flags modifiers = 0; 608 const char *args = *arg; 609 610 if (args == NULL) 611 return modifiers; 612 613 while (*args == '/') 614 { 615 ++args; 616 617 if (*args == '\0') 618 error (_("Missing modifier.")); 619 620 for (; *args; ++args) 621 { 622 if (isspace (*args)) 623 break; 624 625 if (*args == '/') 626 continue; 627 628 switch (*args) 629 { 630 case 'l': 631 modifiers |= RECORD_PRINT_SRC_LINE; 632 break; 633 case 'i': 634 modifiers |= RECORD_PRINT_INSN_RANGE; 635 break; 636 case 'c': 637 modifiers |= RECORD_PRINT_INDENT_CALLS; 638 break; 639 default: 640 error (_("Invalid modifier: %c."), *args); 641 } 642 } 643 644 args = skip_spaces (args); 645 } 646 647 /* Update the argument string. */ 648 *arg = args; 649 650 return modifiers; 651 } 652 653 /* The "record function-call-history" command. */ 654 655 static void 656 cmd_record_call_history (const char *arg, int from_tty) 657 { 658 require_record_target (); 659 660 record_print_flags flags = get_call_history_modifiers (&arg); 661 662 int size = command_size_to_target_size (record_call_history_size); 663 664 if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) 665 target_call_history (size, flags); 666 else if (strcmp (arg, "-") == 0) 667 target_call_history (-size, flags); 668 else 669 { 670 ULONGEST begin, end; 671 672 begin = get_insn_number (&arg); 673 674 if (*arg == ',') 675 { 676 arg = skip_spaces (++arg); 677 678 if (*arg == '+') 679 { 680 arg += 1; 681 size = get_context_size (&arg); 682 683 no_chunk (arg); 684 685 target_call_history_from (begin, size, flags); 686 } 687 else if (*arg == '-') 688 { 689 arg += 1; 690 size = get_context_size (&arg); 691 692 no_chunk (arg); 693 694 target_call_history_from (begin, -size, flags); 695 } 696 else 697 { 698 end = get_insn_number (&arg); 699 700 no_chunk (arg); 701 702 target_call_history_range (begin, end, flags); 703 } 704 } 705 else 706 { 707 no_chunk (arg); 708 709 target_call_history_from (begin, size, flags); 710 } 711 712 dont_repeat (); 713 } 714 } 715 716 /* Helper for "set record instruction-history-size" and "set record 717 function-call-history-size" input validation. COMMAND_VAR is the 718 variable registered in the command as control variable. *SETTING 719 is the real setting the command allows changing. */ 720 721 static void 722 validate_history_size (unsigned int *command_var, unsigned int *setting) 723 { 724 if (*command_var != UINT_MAX && *command_var > INT_MAX) 725 { 726 unsigned int new_value = *command_var; 727 728 /* Restore previous value. */ 729 *command_var = *setting; 730 error (_("integer %u out of range"), new_value); 731 } 732 733 /* Commit new value. */ 734 *setting = *command_var; 735 } 736 737 /* Called by do_setshow_command. We only want values in the 738 [0..INT_MAX] range, while the command's machinery accepts 739 [0..UINT_MAX]. See command_size_to_target_size. */ 740 741 static void 742 set_record_insn_history_size (const char *args, int from_tty, 743 struct cmd_list_element *c) 744 { 745 validate_history_size (&record_insn_history_size_setshow_var, 746 &record_insn_history_size); 747 } 748 749 /* Called by do_setshow_command. We only want values in the 750 [0..INT_MAX] range, while the command's machinery accepts 751 [0..UINT_MAX]. See command_size_to_target_size. */ 752 753 static void 754 set_record_call_history_size (const char *args, int from_tty, 755 struct cmd_list_element *c) 756 { 757 validate_history_size (&record_call_history_size_setshow_var, 758 &record_call_history_size); 759 } 760 761 void _initialize_record (); 762 void 763 _initialize_record () 764 { 765 struct cmd_list_element *c; 766 767 add_setshow_zuinteger_cmd ("record", no_class, &record_debug, 768 _("Set debugging of record/replay feature."), 769 _("Show debugging of record/replay feature."), 770 _("When enabled, debugging output for " 771 "record/replay feature is displayed."), 772 NULL, show_record_debug, &setdebuglist, 773 &showdebuglist); 774 775 add_setshow_uinteger_cmd ("instruction-history-size", no_class, 776 &record_insn_history_size_setshow_var, _("\ 777 Set number of instructions to print in \"record instruction-history\"."), _("\ 778 Show number of instructions to print in \"record instruction-history\"."), _("\ 779 A size of \"unlimited\" means unlimited instructions. The default is 10."), 780 set_record_insn_history_size, NULL, 781 &set_record_cmdlist, &show_record_cmdlist); 782 783 add_setshow_uinteger_cmd ("function-call-history-size", no_class, 784 &record_call_history_size_setshow_var, _("\ 785 Set number of function to print in \"record function-call-history\"."), _("\ 786 Show number of functions to print in \"record function-call-history\"."), _("\ 787 A size of \"unlimited\" means unlimited lines. The default is 10."), 788 set_record_call_history_size, NULL, 789 &set_record_cmdlist, &show_record_cmdlist); 790 791 cmd_list_element *record_cmd 792 = add_prefix_cmd ("record", class_obscure, cmd_record_start, 793 _("Start recording."), 794 &record_cmdlist, 0, &cmdlist); 795 set_cmd_completer (record_cmd, filename_completer); 796 797 add_com_alias ("rec", record_cmd, class_obscure, 1); 798 799 set_show_commands setshow_record_cmds 800 = add_setshow_prefix_cmd ("record", class_support, 801 _("Set record options."), 802 _("Show record options."), 803 &set_record_cmdlist, &show_record_cmdlist, 804 &setlist, &showlist); 805 806 807 add_alias_cmd ("rec", setshow_record_cmds.set, class_obscure, 1, &setlist); 808 add_alias_cmd ("rec", setshow_record_cmds.show, class_obscure, 1, &showlist); 809 810 cmd_list_element *info_record_cmd 811 = add_prefix_cmd ("record", class_support, info_record_command, 812 _("Info record options."), &info_record_cmdlist, 813 0, &infolist); 814 add_alias_cmd ("rec", info_record_cmd, class_obscure, 1, &infolist); 815 816 c = add_cmd ("save", class_obscure, cmd_record_save, 817 _("Save the execution log to a file.\n\ 818 Usage: record save [FILENAME]\n\ 819 Default filename is 'gdb_record.PROCESS_ID'."), 820 &record_cmdlist); 821 set_cmd_completer (c, filename_completer); 822 823 cmd_list_element *record_delete_cmd 824 = add_cmd ("delete", class_obscure, cmd_record_delete, 825 _("Delete the rest of execution log and start recording it \ 826 anew."), 827 &record_cmdlist); 828 add_alias_cmd ("d", record_delete_cmd, class_obscure, 1, &record_cmdlist); 829 add_alias_cmd ("del", record_delete_cmd, class_obscure, 1, &record_cmdlist); 830 831 cmd_list_element *record_stop_cmd 832 = add_cmd ("stop", class_obscure, cmd_record_stop, 833 _("Stop the record/replay target."), 834 &record_cmdlist); 835 add_alias_cmd ("s", record_stop_cmd, class_obscure, 1, &record_cmdlist); 836 837 add_prefix_cmd ("goto", class_obscure, cmd_record_goto, _("\ 838 Restore the program to its state at instruction number N.\n\ 839 Argument is instruction number, as shown by 'info record'."), 840 &record_goto_cmdlist, 1, &record_cmdlist); 841 842 cmd_list_element *record_goto_begin_cmd 843 = add_cmd ("begin", class_obscure, cmd_record_goto_begin, 844 _("Go to the beginning of the execution log."), 845 &record_goto_cmdlist); 846 add_alias_cmd ("start", record_goto_begin_cmd, class_obscure, 1, 847 &record_goto_cmdlist); 848 849 add_cmd ("end", class_obscure, cmd_record_goto_end, 850 _("Go to the end of the execution log."), 851 &record_goto_cmdlist); 852 853 add_cmd ("instruction-history", class_obscure, cmd_record_insn_history, _("\ 854 Print disassembled instructions stored in the execution log.\n\ 855 With a /m or /s modifier, source lines are included (if available).\n\ 856 With a /r modifier, raw instructions in hex are included.\n\ 857 With a /f modifier, function names are omitted.\n\ 858 With a /p modifier, current position markers are omitted.\n\ 859 With no argument, disassembles ten more instructions after the previous \ 860 disassembly.\n\ 861 \"record instruction-history -\" disassembles ten instructions before a \ 862 previous disassembly.\n\ 863 One argument specifies an instruction number as shown by 'info record', and \ 864 ten instructions are disassembled after that instruction.\n\ 865 Two arguments with comma between them specify starting and ending instruction \ 866 numbers to disassemble.\n\ 867 If the second argument is preceded by '+' or '-', it specifies the distance \ 868 from the first argument.\n\ 869 The number of instructions to disassemble can be defined with \"set record \ 870 instruction-history-size\"."), 871 &record_cmdlist); 872 873 add_cmd ("function-call-history", class_obscure, cmd_record_call_history, _("\ 874 Prints the execution history at function granularity.\n\ 875 It prints one line for each sequence of instructions that belong to the same \ 876 function.\n\ 877 Without modifiers, it prints the function name.\n\ 878 With a /l modifier, the source file and line number range is included.\n\ 879 With a /i modifier, the instruction number range is included.\n\ 880 With a /c modifier, the output is indented based on the call stack depth.\n\ 881 With no argument, prints ten more lines after the previous ten-line print.\n\ 882 \"record function-call-history -\" prints ten lines before a previous ten-line \ 883 print.\n\ 884 One argument specifies a function number as shown by 'info record', and \ 885 ten lines are printed after that function.\n\ 886 Two arguments with comma between them specify a range of functions to print.\n\ 887 If the second argument is preceded by '+' or '-', it specifies the distance \ 888 from the first argument.\n\ 889 The number of functions to print can be defined with \"set record \ 890 function-call-history-size\"."), 891 &record_cmdlist); 892 893 /* Sync command control variables. */ 894 record_insn_history_size_setshow_var = record_insn_history_size; 895 record_call_history_size_setshow_var = record_call_history_size; 896 } 897