# trace gdb ![](https://i.imgur.com/zMdFGMb.png) 最近在看能不能在gdb攔截一些有關於runtime的程式的pc address ,gdb背後底層原理是ptrace,大概從各個檔案裏面去埋print(printf_unfiltered (("hello\n"));),來稍微紀錄一下 ## arch arch 資料夾存放的是有關於指令架構的一些function也就是當觸發斷點時候可以透過不同的arch將不同的register範圍讀入或讀出 例如 i386.c ```c= target_desc * i386_create_target_description (uint64_t xcr0, bool is_linux, bool segments) { target_desc_up tdesc = allocate_target_description (); #ifndef IN_PROCESS_AGENT set_tdesc_architecture (tdesc.get (), "i386"); if (is_linux) set_tdesc_osabi (tdesc.get (), "GNU/Linux"); #endif long regnum = 0; if (xcr0 & X86_XSTATE_X87) regnum = create_feature_i386_32bit_core (tdesc.get (), regnum); if (xcr0 & X86_XSTATE_SSE) regnum = create_feature_i386_32bit_sse (tdesc.get (), regnum); if (is_linux) regnum = create_feature_i386_32bit_linux (tdesc.get (), regnum); if (segments) regnum = create_feature_i386_32bit_segments (tdesc.get (), regnum); if (xcr0 & X86_XSTATE_AVX) regnum = create_feature_i386_32bit_avx (tdesc.get (), regnum); if (xcr0 & X86_XSTATE_MPX) regnum = create_feature_i386_32bit_mpx (tdesc.get (), regnum); if (xcr0 & X86_XSTATE_AVX512) regnum = create_feature_i386_32bit_avx512 (tdesc.get (), regnum); if (xcr0 & X86_XSTATE_PKRU) regnum = create_feature_i386_pkeys (tdesc.get (), regnum); return tdesc.release (); } ``` ... 這部分可以看到在arch資料夾根目錄可以找到相對應指令架構的描述 依我的了解debug 可以remote target 和 local端 不論如何,當發生中斷 SIGTRAP,SIGINT,、、 都會將當前的程式控制權交還給gdb,也就是程式的一些pc register都存到一個gdbarch 的結構 而arch 就算一層封裝對應到不同的arch 而讓gdb可以根據當前的使用的gdb裝置的arch更方便的存入和讀出gdbarch 然後 ### amd64-linux-tdep.c ```c= void _initialize_amd64_linux_tdep (); void _initialize_amd64_linux_tdep () { gdbarch_register_osabi (bfd_arch_i386, bfd_mach_x86_64, GDB_OSABI_LINUX, amd64_linux_init_abi); gdbarch_register_osabi (bfd_arch_i386, bfd_mach_x64_32, GDB_OSABI_LINUX, amd64_x32_linux_init_abi); } ``` ```c= struct gdbarch *gdbarch; const gdb_byte *sigtramp_code; CORE_ADDR pc = get_frame_pc (this_frame); gdb_byte buf[LINUX_SIGTRAMP_LEN]; ``` ```c= gdbarch = get_frame_arch (this_frame); if (gdbarch_ptr_bit (gdbarch) == 32) sigtramp_code = amd64_x32_linux_sigtramp_code; else sigtramp_code = amd64_linux_sigtramp_code; if (memcmp (buf, sigtramp_code, LINUX_SIGTRAMP_LEN) != 0) return 0; ``` 可以在某些片段發現都是再透過get_frame_pc,get_frame_arch 去取得被中斷的程式 fream 以便可以得到對應arch 程式當下的register狀態. ```c= /* From <asm/sigcontext.h>. */ static int amd64_linux_sc_reg_offset[] = { 13 * 8, /* %rax */ 11 * 8, /* %rbx */ 14 * 8, /* %rcx */ 12 * 8, /* %rdx */ 9 * 8, /* %rsi */ 8 * 8, /* %rdi */ 10 * 8, /* %rbp */ 15 * 8, /* %rsp */ 0 * 8, /* %r8 */ 1 * 8, /* %r9 */ 2 * 8, /* %r10 */ 3 * 8, /* %r11 */ 4 * 8, /* %r12 */ 5 * 8, /* %r13 */ 6 * 8, /* %r14 */ 7 * 8, /* %r15 */ 16 * 8, /* %rip */ 17 * 8, /* %eflags */ /* FIXME: kettenis/2002030531: The registers %cs, %fs and %gs are available in `struct sigcontext'. However, they only occupy two bytes instead of four, which makes using them here rather difficult. Leave them out for now. */ -1, /* %cs */ -1, /* %ss */ -1, /* %ds */ -1, /* %es */ -1, /* %fs */ -1 /* %gs */ }; ``` socket init ```c= amd64_x32_linux_record_tdep.size_statfs64 = 120; amd64_x32_linux_record_tdep.size_sockaddr = 16; amd64_x32_linux_record_tdep.size_int = gdbarch_int_bit (gdbarch) / TARGET_CHAR_BIT; ``` ### amd64-linux-nat.c net 像是一個interface ,因為amd64不只有對應linux 還有fsd ,bsd... ```c= void _initialize_amd64_linux_nat (); void _initialize_amd64_linux_nat () { amd64_native_gregset32_reg_offset = amd64_linux_gregset32_reg_offset; amd64_native_gregset32_num_regs = I386_LINUX_NUM_REGS; amd64_native_gregset64_reg_offset = amd64_linux_gregset_reg_offset; amd64_native_gregset64_num_regs = AMD64_LINUX_NUM_REGS; gdb_assert (ARRAY_SIZE (amd64_linux_gregset32_reg_offset) == amd64_native_gregset32_num_regs); linux_target = &the_amd64_linux_nat_target; /* Add the target. */ add_inf_child_target (linux_target); } ``` # debug gdb using gdb 熟悉後,可以再用gdb在去debug gdb ![](https://i.imgur.com/Ywsdq8i.png) 如果你是最上層的gdb你的cli 會顯示你是top ![](https://i.imgur.com/CML6nwI.png) ```bahs= #3 0x000000000098df7e in gdb_wait_for_event (block=1) at event-loop.cc:594 #4 gdb_do_one_event (mstimeout=mstimeout@entry=-1) at event-loop.cc:264 #5 0x00000000006af19a in start_event_loop () at main.c:412 #6 captured_command_loop () at main.c:476 #7 0x00000000006b0a65 in captured_main (data=data@entry=0x7ffc5a3546c0) at main.c:1320 #8 gdb_main (args=args@entry=0x7ffc5a3546f0) at main.c:1339 #9 0x0000000000432d95 in main (argc=<optimized out>, argv=<optimized out>) at gdb.c:32 ``` ```bash= Starting program: /root/x21321219/functionpro/a.out signal-name signal-name signal-name Breakpoint 1, main () at test.c:32 32 int counter = 0; (gdb) ``` 只要在想要觀看gdb的部分就直接在top gdb直接ctrl+c就可以暫停觀看call stack了 以剛剛的輸出也就是觸發中斷後其實gdb會一直掛在gdb_wait_for_event等待下一次指令或者中斷發生, # cli-interp.c ```c= /* Observer for the signal_received notification. */ static void cli_base_on_signal_received (enum gdb_signal siggnal) { SWITCH_THRU_ALL_UIS () { cli_interp_base *cli = as_cli_interp_base (top_level_interpreter ()); if (cli == nullptr) continue; print_signal_received_reason (cli->interp_ui_out (), siggnal); } } ``` 當gdb檢測到中斷觸發的時候,例如使用者ctrl+c 程式會立刻中斷,然後我們會看到 Program received signal SIGINT, Interrupt. 或者SIGTRAP等等, ## print_signal_received_reason 在後半部可以看到,這邊又是根據regcache->arch () 去得到 gdbarch,大概可以猜想的到 gdbarch假設發生中斷,一些資訊就是往這個結構做填充. ,還有一些程式碼跑完阿一些中斷處理function 都在這個檔案內 ```C= struct regcache *regcache = get_current_regcache (); struct gdbarch *gdbarch = regcache->arch (); if (gdbarch_report_signal_info_p (gdbarch)){ gdbarch_report_signal_info (gdbarch, uiout, siggnal); ``` ```C void print_signal_received_reason (struct ui_out *uiout, enum gdb_signal siggnal) { struct thread_info *thr = inferior_thread (); infrun_debug_printf ("signal = %s", gdb_signal_to_string (siggnal)); annotate_signal (); if (uiout->is_mi_like_p ()) ; else if (show_thread_that_caused_stop ()) { uiout->text ("\nThread "); uiout->field_string ("thread-id", print_thread_id (thr)); const char *name = thread_name (thr); if (name != nullptr) { uiout->text (" \""); uiout->field_string ("name", name); uiout->text ("\""); } } else uiout->text ("\nProgram"); if (siggnal == GDB_SIGNAL_0 && !uiout->is_mi_like_p ()) uiout->text (" stopped"); else { uiout->text (" received signal "); annotate_signal_name (); if (uiout->is_mi_like_p ()) uiout->field_string ("reason", async_reason_lookup (EXEC_ASYNC_SIGNAL_RECEIVED)); uiout->field_string ("signal-name", gdb_signal_to_name (siggnal)); annotate_signal_name_end (); uiout->text (", "); annotate_signal_string (); uiout->field_string ("signal-meaning", gdb_signal_to_string (siggnal)); struct regcache *regcache = get_current_regcache (); struct gdbarch *gdbarch = regcache->arch (); if (gdbarch_report_signal_info_p (gdbarch)){ gdbarch_report_signal_info (gdbarch, uiout, siggnal); } annotate_signal_string_end (); } uiout->text (".\n"); } ``` # frame.c 剛剛有看到get_frame_pc等等fucntion 都是從gdbarch結構拿 ``` CORE_ADDR get_frame_pc (frame_info_ptr frame) { gdb_assert (frame->next != NULL); return frame_unwind_pc (frame_info_ptr (frame->next)); } static CORE_ADDR frame_unwind_pc (frame_info_ptr this_frame) { if (this_frame->prev_pc.status == CC_UNKNOWN) { struct gdbarch *prev_gdbarch; CORE_ADDR pc = 0; bool pc_p = false; /* The right way. The `pure' way. The one true way. This method depends solely on the register-unwind code to determine the value of registers in THIS frame, and hence the value of this frame's PC (resume address). A typical implementation is no more than: frame_unwind_register (this_frame, ISA_PC_REGNUM, buf); return extract_unsigned_integer (buf, size of ISA_PC_REGNUM); Note: this method is very heavily dependent on a correct register-unwind implementation, it pays to fix that method first; this method is frame type agnostic, since it only deals with register values, it works with any frame. This is all in stark contrast to the old FRAME_SAVED_PC which would try to directly handle all the different ways that a PC could be unwound. */ prev_gdbarch = frame_unwind_arch (this_frame); try { pc = gdbarch_unwind_pc (prev_gdbarch, this_frame); pc_p = true; } catch (const gdb_exception_error &ex) { if (ex.error == NOT_AVAILABLE_ERROR) { this_frame->prev_pc.status = CC_UNAVAILABLE; frame_debug_printf ("this_frame=%d -> <unavailable>", this_frame->level); } else if (ex.error == OPTIMIZED_OUT_ERROR) { this_frame->prev_pc.status = CC_NOT_SAVED; frame_debug_printf ("this_frame=%d -> <not saved>", this_frame->level); } else throw; } if (pc_p) { this_frame->prev_pc.value = pc; this_frame->prev_pc.status = CC_VALUE; frame_debug_printf ("this_frame=%d -> %s", this_frame->level, hex_string (this_frame->prev_pc.value)); } } if (this_frame->prev_pc.status == CC_VALUE) return this_frame->prev_pc.value; else if (this_frame->prev_pc.status == CC_UNAVAILABLE) throw_error (NOT_AVAILABLE_ERROR, _("PC not available")); else if (this_frame->prev_pc.status == CC_NOT_SAVED) throw_error (OPTIMIZED_OUT_ERROR, _("PC not saved")); else internal_error ("unexpected prev_pc status: %d", (int) this_frame->prev_pc.status); } ``` # gdbarch ```c= CORE_ADDR gdbarch_unwind_pc (struct gdbarch *gdbarch, frame_info_ptr next_frame) { gdb_assert (gdbarch != NULL); gdb_assert (gdbarch->unwind_pc != NULL); if (gdbarch_debug >= 2) gdb_printf (gdb_stdlog, "gdbarch_unwind_pc called\n"); return gdbarch->unwind_pc (gdbarch, next_frame); } gdbarch_unwind_pc_ftype *unwind_pc = default_unwind_pc; ``` ```c= /* See frame-unwind.h. */ CORE_ADDR default_unwind_pc (struct gdbarch *gdbarch, frame_info_ptr next_frame) { int pc_regnum = gdbarch_pc_regnum (gdbarch); CORE_ADDR pc = frame_unwind_register_unsigned (next_frame, pc_regnum); pc = gdbarch_addr_bits_remove (gdbarch, pc); return pc; } ``` ```c= ULONGEST frame_unwind_register_unsigned (frame_info_ptr next_frame, int regnum) { struct gdbarch *gdbarch = frame_unwind_arch (next_frame); enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); int size = register_size (gdbarch, regnum); struct value *value = frame_unwind_register_value (next_frame, regnum); gdb_assert (value != NULL); if (value->optimized_out ()) { throw_error (OPTIMIZED_OUT_ERROR, _("Register %d was not saved"), regnum); } if (!value->entirely_available ()) { throw_error (NOT_AVAILABLE_ERROR, _("Register %d is not available"), regnum); } ULONGEST r = extract_unsigned_integer (value->contents_all ().data (), size, byte_order); release_value (value); return r; } ``` ```c= struct gdbarch * frame_unwind_arch (frame_info_ptr next_frame) { if (!next_frame->prev_arch.p) { struct gdbarch *arch; if (next_frame->unwind == NULL) frame_unwind_find_by_frame (next_frame, &next_frame->prologue_cache); if (next_frame->unwind->prev_arch != NULL) arch = next_frame->unwind->prev_arch (next_frame, &next_frame->prologue_cache); else arch = get_frame_arch (next_frame); next_frame->prev_arch.arch = arch; next_frame->prev_arch.p = true; frame_debug_printf ("next_frame=%d -> %s", next_frame->level, gdbarch_bfd_arch_info (arch)->printable_name); } return next_frame->prev_arch.arch; } ``` 可以發現這邊要取得pc address,根據上面的順序還是要先找到對應的arch 才能得到相對應的arch register結構, 假設想在gdb各處取得pc address結構可不可以? ```c= frame_info_ptr frame; struct gdbarch *gdbarch; gdbarch = get_frame_arch (frame); gdbarch_print_registers_info (gdbarch, gdb_stdout, frame, -1, fpregs); ``` 答案是不行,get_frame_arch 這個function就有做攔截 # get_frame_arch 要拿到frame還要看你在何時你的gdb 處在哪一個 level,當你不處在commad環節就會出錯 gdb_assert (m_cached_level >= -1); 或者 gdb_assert (frame_id_p (m_cached_id)); 根本拿不到frame ```c= /* See frame-info-ptr.h. */ frame_info * frame_info_ptr::reinflate () const { /* Ensure we have a valid frame level (sentinel frame or above). */ gdb_assert (m_cached_level >= -1); if (m_ptr != nullptr) { /* The frame_info wasn't invalidated, no need to reinflate. */ return m_ptr; } if (m_cached_id.user_created_p) m_ptr = create_new_frame (m_cached_id).get (); else { /* Frame #0 needs special handling, see comment in select_frame. */ if (m_cached_level == 0) m_ptr = get_current_frame ().get (); else { /* If we reach here without a valid frame id, it means we are trying to reinflate a frame whose id was not know at construction time. We're probably trying to reinflate a frame while computing its id which is not possible, and would indicate a problem with GDB. */ gdb_assert (frame_id_p (m_cached_id)); m_ptr = frame_find_by_id (m_cached_id).get (); } } gdb_assert (m_ptr != nullptr); return m_ptr; } ``` 假設你在gdb合理的情況下取得frame那麼就可以來print register info結構 # gdbarch_print_registers_info ```c= gdbarch_print_registers_info (gdbarch, gdb_stdout, frame, -1, fpregs); ``` ```c= /* Print out the machine register regnum. If regnum is -1, print all registers (print_all == 1) or all non-float and non-vector registers (print_all == 0). For most machines, having all_registers_info() print the register(s) one per line is good enough. If a different format is required, (eg, for MIPS or Pyramid 90x, which both have lots of regs), or there is an existing convention for showing all the registers, define the architecture method PRINT_REGISTERS_INFO to provide that format. */ void default_print_registers_info (struct gdbarch *gdbarch, struct ui_file *file, frame_info_ptr frame, int regnum, int print_all) { int i; const int numregs = gdbarch_num_cooked_regs (gdbarch); for (i = 0; i < numregs; i++) { /* Decide between printing all regs, non-float / vector regs, or specific reg. */ if (regnum == -1) { if (print_all) { if (!gdbarch_register_reggroup_p (gdbarch, i, all_reggroup)) continue; } else { if (!gdbarch_register_reggroup_p (gdbarch, i, general_reggroup)) continue; } } else { if (i != regnum) continue; } /* If the register name is empty, it is undefined for this processor, so don't display anything. */ if (*(gdbarch_register_name (gdbarch, i)) == '\0') continue; default_print_one_register_info (file, gdbarch_register_name (gdbarch, i), value_of_register (i, frame)); } } ``` 其實這邊可以看到gdbarch_register_name 去拿一開始架構的register name做dump而已, ```c= static inline int gdbarch_num_cooked_regs (gdbarch *arch) { return gdbarch_num_regs (arch) + gdbarch_num_pseudo_regs (arch); } ``` 看你有幾個register ```c= const int numregs = gdbarch_num_cooked_regs (gdbarch); for (i = 0; i < numregs; i++) { } ``` ```c= const char * gdbarch_register_name (struct gdbarch *gdbarch, int regnr) { gdb_assert (gdbarch != NULL); gdb_assert (gdbarch->register_name != NULL); gdb_assert (regnr >= 0); gdb_assert (regnr < gdbarch_num_cooked_regs (gdbarch)); if (gdbarch_debug >= 2) gdb_printf (gdb_stdlog, "gdbarch_register_name called\n"); auto result = gdbarch->register_name (gdbarch, regnr); gdb_assert (result != nullptr); return result; } ``` # breakpoint breakpoint init ```c= void _initialize_breakpoint (); void _initialize_breakpoint () { struct cmd_list_element *c; gdb::observers::solib_unloaded.attach (disable_breakpoints_in_unloaded_shlib, "breakpoint"); gdb::observers::free_objfile.attach (disable_breakpoints_in_freed_objfile, "breakpoint"); gdb::observers::memory_changed.attach (invalidate_bp_value_on_memory_change, "breakpoint"); breakpoint_chain = 0; /* Don't bother to call set_breakpoint_count. $bpnum isn't useful before a breakpoint is set. */ breakpoint_count = 0; tracepoint_count = 0; add_com ("ignore", class_breakpoint, ignore_command, _("\ Set ignore-count of breakpoint number N to COUNT.\n\ Usage is `ignore N COUNT'.")); commands_cmd_element = add_com ("commands", class_breakpoint, commands_command, _("\ Set commands to be executed when the given breakpoints are hit.\n\ Give a space-separated breakpoint list as argument after \"commands\".\n\ A list element can be a breakpoint number (e.g. `5') or a range of numbers\n\ (e.g. `5-7').\n\ With no argument, the targeted breakpoint is the last one set.\n\ The commands themselves follow starting on the next line.\n\ Type a line containing \"end\" to indicate the end of them.\n\ Give \"silent\" as the first line to make the breakpoint silent;\n\ then no output is printed when it is hit, except what the commands print.")); const auto cc_opts = make_condition_command_options_def_group (nullptr); static std::string condition_command_help = gdb::option::build_help (_("\ Specify breakpoint number N to break only if COND is true.\n\ Usage is `condition [OPTION] N COND', where N is an integer and COND\n\ is an expression to be evaluated whenever breakpoint N is reached.\n\ \n\ Options:\n\ %OPTIONS%"), cc_opts); c = add_com ("condition", class_breakpoint, condition_command, condition_command_help.c_str ()); set_cmd_completer_handle_brkchars (c, condition_completer); c = add_com ("tbreak", class_breakpoint, tbreak_command, _("\ Set a temporary breakpoint.\n\ Like \"break\" except the breakpoint is only temporary,\n\ so it will be deleted when hit. Equivalent to \"break\" followed\n\ by using \"enable delete\" on the breakpoint number.\n\ \n" BREAK_ARGS_HELP ("tbreak"))); set_cmd_completer (c, location_completer); c = add_com ("hbreak", class_breakpoint, hbreak_command, _("\ Set a hardware assisted breakpoint.\n\ Like \"break\" except the breakpoint requires hardware support,\n\ some target hardware may not have this support.\n\ \n" BREAK_ARGS_HELP ("hbreak"))); set_cmd_completer (c, location_completer); c = add_com ("thbreak", class_breakpoint, thbreak_command, _("\ Set a temporary hardware assisted breakpoint.\n\ Like \"hbreak\" except the breakpoint is only temporary,\n\ so it will be deleted when hit.\n\ \n" BREAK_ARGS_HELP ("thbreak"))); set_cmd_completer (c, location_completer); cmd_list_element *enable_cmd = add_prefix_cmd ("enable", class_breakpoint, enable_command, _("\ Enable all or some breakpoints.\n\ Usage: enable [BREAKPOINTNUM]...\n\ Give breakpoint numbers (separated by spaces) as arguments.\n\ With no subcommand, breakpoints are enabled until you command otherwise.\n\ This is used to cancel the effect of the \"disable\" command.\n\ With a subcommand you can enable temporarily."), &enablelist, 1, &cmdlist); add_com_alias ("en", enable_cmd, class_breakpoint, 1); add_prefix_cmd ("breakpoints", class_breakpoint, enable_command, _("\ Enable all or some breakpoints.\n\ Usage: enable breakpoints [BREAKPOINTNUM]...\n\ Give breakpoint numbers (separated by spaces) as arguments.\n\ This is used to cancel the effect of the \"disable\" command.\n\ May be abbreviated to simply \"enable\"."), &enablebreaklist, 1, &enablelist); add_cmd ("once", no_class, enable_once_command, _("\ Enable some breakpoints for one hit.\n\ Usage: enable breakpoints once BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion, it becomes disabled."), &enablebreaklist); add_cmd ("delete", no_class, enable_delete_command, _("\ Enable some breakpoints and delete when hit.\n\ Usage: enable breakpoints delete BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion, it is deleted."), &enablebreaklist); add_cmd ("count", no_class, enable_count_command, _("\ Enable some breakpoints for COUNT hits.\n\ Usage: enable breakpoints count COUNT BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion,\n\ the count is decremented; when it reaches zero, the breakpoint is disabled."), &enablebreaklist); add_cmd ("delete", no_class, enable_delete_command, _("\ Enable some breakpoints and delete when hit.\n\ Usage: enable delete BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion, it is deleted."), &enablelist); add_cmd ("once", no_class, enable_once_command, _("\ Enable some breakpoints for one hit.\n\ Usage: enable once BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion, it becomes disabled."), &enablelist); add_cmd ("count", no_class, enable_count_command, _("\ Enable some breakpoints for COUNT hits.\n\ Usage: enable count COUNT BREAKPOINTNUM...\n\ If a breakpoint is hit while enabled in this fashion,\n\ the count is decremented; when it reaches zero, the breakpoint is disabled."), &enablelist); cmd_list_element *disable_cmd = add_prefix_cmd ("disable", class_breakpoint, disable_command, _("\ Disable all or some breakpoints.\n\ Usage: disable [BREAKPOINTNUM]...\n\ Arguments are breakpoint numbers with spaces in between.\n\ To disable all breakpoints, give no argument.\n\ A disabled breakpoint is not forgotten, but has no effect until re-enabled."), &disablelist, 1, &cmdlist); add_com_alias ("dis", disable_cmd, class_breakpoint, 1); add_com_alias ("disa", disable_cmd, class_breakpoint, 1); add_cmd ("breakpoints", class_breakpoint, disable_command, _("\ Disable all or some breakpoints.\n\ Usage: disable breakpoints [BREAKPOINTNUM]...\n\ Arguments are breakpoint numbers with spaces in between.\n\ To disable all breakpoints, give no argument.\n\ A disabled breakpoint is not forgotten, but has no effect until re-enabled.\n\ This command may be abbreviated \"disable\"."), &disablelist); cmd_list_element *delete_cmd = add_prefix_cmd ("delete", class_breakpoint, delete_command, _("\ Delete all or some breakpoints.\n\ Usage: delete [BREAKPOINTNUM]...\n\ Arguments are breakpoint numbers with spaces in between.\n\ ... ``` 插入斷點 ```c= int code_breakpoint::insert_location (struct bp_location *bl) { CORE_ADDR addr = bl->target_info.reqstd_address; bl->target_info.kind = breakpoint_kind (bl, &addr); bl->target_info.placed_address = addr; int result; if (bl->loc_type == bp_loc_hardware_breakpoint) result = target_insert_hw_breakpoint (bl->gdbarch, &bl->target_info); else result = target_insert_breakpoint (bl->gdbarch, &bl->target_info); if (result == 0 && bl->probe.prob != nullptr) { /* The insertion was successful, now let's set the probe's semaphore if needed. */ bl->probe.prob->set_semaphore (bl->probe.objfile, bl->gdbarch); } return result; } ``` 看是hardware breakpoint還是 普通斷點 ```c= result = target_insert_hw_breakpoint (bl->gdbarch, &bl->target_info); else result = target_insert_breakpoint (bl->gdbarch, &bl->target_info); ``` 觸發斷點,可以看到觸發斷點當然要檢查相對應的address是否對應 ```c= int code_breakpoint::breakpoint_hit (const struct bp_location *bl, const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { if (ws.kind () != TARGET_WAITKIND_STOPPED || ws.sig () != GDB_SIGNAL_TRAP) return 0; if (!breakpoint_address_match (bl->pspace->aspace, bl->address, aspace, bp_addr)) return 0; if (overlay_debugging /* unmapped overlay section */ && section_is_overlay (bl->section) && !section_is_mapped (bl->section)) return 0; return 1; } int dprintf_breakpoint::breakpoint_hit (const struct bp_location *bl, const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { if (dprintf_style == dprintf_style_agent && target_can_run_breakpoint_commands ()) { /* An agent-style dprintf never causes a stop. If we see a trap for this address it must be for a breakpoint that happens to be set at the same address. */ return 0; } return this->ordinary_breakpoint::breakpoint_hit (bl, aspace, bp_addr, ws); } ``` 比較有趣的是觸發竟然不是相對應的pc address ```c= static bool breakpoint_address_match_range (const address_space *aspace1, CORE_ADDR addr1, int len1, const address_space *aspace2, CORE_ADDR addr2) { return ((gdbarch_has_global_breakpoints (target_gdbarch ()) || aspace1 == aspace2) && addr2 >= addr1 && addr2 < addr1 + len1); } ``` 再往上追bpstat_check_location ```c= /* Return true if it looks like target has stopped due to hitting breakpoint location BL. This function does not check if we should stop, only if BL explains the stop. */ static bool bpstat_check_location (const struct bp_location *bl, const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { struct breakpoint *b = bl->owner; /* BL is from an existing breakpoint. */ gdb_assert (b != NULL); return b->breakpoint_hit (bl, aspace, bp_addr, ws); } ``` 這邊是程式看來已經停下來了,這邊會再檢查一次address是否正確,看來還有一層 build_bpstat_chain這邊看來就是還有一個地方是存放所有breakpoints,在這邊就可以看到一些比較常看到的指令set_breakpoint_condition,delete_breakpoint等等 ```c= /* See breakpoint.h. */ bpstat * build_bpstat_chain (const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { bpstat *bs_head = nullptr, **bs_link = &bs_head; for (breakpoint *b : all_breakpoints ()) { if (!breakpoint_enabled (b)) continue; for (bp_location *bl : b->locations ()) { /* For hardware watchpoints, we look only at the first location. The watchpoint_check function will work on the entire expression, not the individual locations. For read watchpoints, the watchpoints_triggered function has checked all locations already. */ if (b->type == bp_hardware_watchpoint && bl != b->loc) break; if (!bl->enabled || bl->disabled_by_cond || bl->shlib_disabled) continue; if (!bpstat_check_location (bl, aspace, bp_addr, ws)) continue; /* Come here if it's a watchpoint, or if the break address matches. */ bpstat *bs = new bpstat (bl, &bs_link); /* Alloc a bpstat to explain stop. */ /* Assume we stop. Should we find a watchpoint that is not actually triggered, or if the condition of the breakpoint evaluates as false, we'll reset 'stop' to 0. */ bs->stop = true; bs->print = true; /* If this is a scope breakpoint, mark the associated watchpoint as triggered so that we will handle the out-of-scope event. We'll get to the watchpoint next iteration. */ if (b->type == bp_watchpoint_scope && b->related_breakpoint != b) { struct watchpoint *w = (struct watchpoint *) b->related_breakpoint; w->watchpoint_triggered = watch_triggered_yes; } } } /* Check if a moribund breakpoint explains the stop. */ if (!target_supports_stopped_by_sw_breakpoint () || !target_supports_stopped_by_hw_breakpoint ()) { for (bp_location *loc : moribund_locations) { if (breakpoint_location_address_match (loc, aspace, bp_addr) && need_moribund_for_location_type (loc)) { bpstat *bs = new bpstat (loc, &bs_link); /* For hits of moribund locations, we should just proceed. */ bs->stop = false; bs->print = false; bs->print_it = print_it_noop; } } } return bs_head; } ``` # infrun.c 當觸發SINGNAL時候應該會由這裡來判斷是什麼SINGNAL再決定做什麼事 ```c= /* Come here when the program has stopped with a signal. */ static void handle_signal_stop (struct execution_control_state *ecs) { frame_info_ptr frame; struct gdbarch *gdbarch; int stopped_by_watchpoint; enum stop_kind stop_soon; int random_signal; gdb_assert (ecs->ws.kind () == TARGET_WAITKIND_STOPPED); ecs->event_thread->set_stop_signal (ecs->ws.sig ()); /* Do we need to clean up the state of a thread that has completed a displaced single-step? (Doing so usually affects the PC, so do it here, before we set stop_pc.) */ if (finish_step_over (ecs)) return; /* If we either finished a single-step or hit a breakpoint, but the user wanted this thread to be stopped, pretend we got a SIG0 (generic unsignaled stop). */ if (ecs->event_thread->stop_requested && ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP) ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); ecs->event_thread->set_stop_pc (regcache_read_pc (get_thread_regcache (ecs->event_thread))); context_switch (ecs); if (deprecated_context_hook) deprecated_context_hook (ecs->event_thread->global_num); if (debug_infrun) { struct regcache *regcache = get_thread_regcache (ecs->event_thread); struct gdbarch *reg_gdbarch = regcache->arch (); infrun_debug_printf ("stop_pc=%s", paddress (reg_gdbarch, ecs->event_thread->stop_pc ())); if (target_stopped_by_watchpoint ()) { CORE_ADDR addr; infrun_debug_printf ("stopped by watchpoint"); if (target_stopped_data_address (current_inferior ()->top_target (), &addr)) infrun_debug_printf ("stopped data address=%s", paddress (reg_gdbarch, addr)); else infrun_debug_printf ("(no data address available)"); } } /* This is originated from start_remote(), start_inferior() and shared libraries hook functions. */ stop_soon = get_inferior_stop_soon (ecs); if (stop_soon == STOP_QUIETLY || stop_soon == STOP_QUIETLY_REMOTE) { infrun_debug_printf ("quietly stopped"); stop_print_frame = true; stop_waiting (ecs); return; } /* This originates from attach_command(). We need to overwrite the stop_signal here, because some kernels don't ignore a SIGSTOP in a subsequent ptrace(PTRACE_CONT,SIGSTOP) call. See more comments in inferior.h. On the other hand, if we get a non-SIGSTOP, report it to the user - assume the backend will handle the SIGSTOP if it should show up later. Also consider that the attach is complete when we see a SIGTRAP. Some systems (e.g. Windows), and stubs supporting target extended-remote report it instead of a SIGSTOP (e.g. gdbserver). We already rely on SIGTRAP being our signal, so this is no exception. Also consider that the attach is complete when we see a GDB_SIGNAL_0. In non-stop mode, GDB will explicitly tell the target to stop all threads of the inferior, in case the low level attach operation doesn't stop them implicitly. If they weren't stopped implicitly, then the stub will report a GDB_SIGNAL_0, meaning: stopped for no particular reason other than GDB's request. */ if (stop_soon == STOP_QUIETLY_NO_SIGSTOP && (ecs->event_thread->stop_signal () == GDB_SIGNAL_STOP || ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP || ecs->event_thread->stop_signal () == GDB_SIGNAL_0)) { stop_print_frame = true; stop_waiting (ecs); ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); return; } /* At this point, get hold of the now-current thread's frame. */ frame = get_current_frame (); gdbarch = get_frame_arch (frame); /* Pull the single step breakpoints out of the target. */ if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP) { struct regcache *regcache; CORE_ADDR pc; regcache = get_thread_regcache (ecs->event_thread); const address_space *aspace = regcache->aspace (); pc = regcache_read_pc (regcache); /* However, before doing so, if this single-step breakpoint was actually for another thread, set this thread up for moving past it. */ if (!thread_has_single_step_breakpoint_here (ecs->event_thread, aspace, pc)) { if (single_step_breakpoint_inserted_here_p (aspace, pc)) { infrun_debug_printf ("[%s] hit another thread's single-step " "breakpoint", ecs->ptid.to_string ().c_str ()); ecs->hit_singlestep_breakpoint = 1; } } else { infrun_debug_printf ("[%s] hit its single-step breakpoint", ecs->ptid.to_string ().c_str ()); } } delete_just_stopped_threads_single_step_breakpoints (); if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && ecs->event_thread->control.trap_expected && ecs->event_thread->stepping_over_watchpoint) stopped_by_watchpoint = 0; else stopped_by_watchpoint = watchpoints_triggered (ecs->ws); /* If necessary, step over this watchpoint. We'll be back to display it in a moment. */ if (stopped_by_watchpoint && (target_have_steppable_watchpoint () || gdbarch_have_nonsteppable_watchpoint (gdbarch))) { /* At this point, we are stopped at an instruction which has attempted to write to a piece of memory under control of a watchpoint. The instruction hasn't actually executed yet. If we were to evaluate the watchpoint expression now, we would get the old value, and therefore no change would seem to have occurred. In order to make watchpoints work `right', we really need to complete the memory write, and then evaluate the watchpoint expression. We do this by single-stepping the target. It may not be necessary to disable the watchpoint to step over it. For example, the PA can (with some kernel cooperation) single step over a watchpoint without disabling the watchpoint. It is far more common to need to disable a watchpoint to step the inferior over it. If we have non-steppable watchpoints, we must disable the current watchpoint; it's simplest to disable all watchpoints. Any breakpoint at PC must also be stepped over -- if there's one, it will have already triggered before the watchpoint triggered, and we either already reported it to the user, or it didn't cause a stop and we called keep_going. In either case, if there was a breakpoint at PC, we must be trying to step past it. */ ecs->event_thread->stepping_over_watchpoint = 1; keep_going (ecs); return; } ecs->event_thread->stepping_over_breakpoint = 0; ecs->event_thread->stepping_over_watchpoint = 0; bpstat_clear (&ecs->event_thread->control.stop_bpstat); ecs->event_thread->control.stop_step = 0; stop_print_frame = true; stopped_by_random_signal = 0; bpstat *stop_chain = nullptr; /* Hide inlined functions starting here, unless we just performed stepi or nexti. After stepi and nexti, always show the innermost frame (not any inline function call sites). */ if (ecs->event_thread->control.step_range_end != 1) { const address_space *aspace = get_thread_regcache (ecs->event_thread)->aspace (); /* skip_inline_frames is expensive, so we avoid it if we can determine that the address is one where functions cannot have been inlined. This improves performance with inferiors that load a lot of shared libraries, because the solib event breakpoint is defined as the address of a function (i.e. not inline). Note that we have to check the previous PC as well as the current one to catch cases when we have just single-stepped off a breakpoint prior to reinstating it. Note that we're assuming that the code we single-step to is not inline, but that's not definitive: there's nothing preventing the event breakpoint function from containing inlined code, and the single-step ending up there. If the user had set a breakpoint on that inlined code, the missing skip_inline_frames call would break things. Fortunately that's an extremely unlikely scenario. */ if (!pc_at_non_inline_function (aspace, ecs->event_thread->stop_pc (), ecs->ws) && !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && ecs->event_thread->control.trap_expected && pc_at_non_inline_function (aspace, ecs->event_thread->prev_pc, ecs->ws))) { stop_chain = build_bpstat_chain (aspace, ecs->event_thread->stop_pc (), ecs->ws); skip_inline_frames (ecs->event_thread, stop_chain); /* Re-fetch current thread's frame in case that invalidated the frame cache. */ frame = get_current_frame (); gdbarch = get_frame_arch (frame); } } if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && ecs->event_thread->control.trap_expected && gdbarch_single_step_through_delay_p (gdbarch) && currently_stepping (ecs->event_thread)) { /* We're trying to step off a breakpoint. Turns out that we're also on an instruction that needs to be stepped multiple times before it's been fully executing. E.g., architectures with a delay slot. It needs to be stepped twice, once for the instruction and once for the delay slot. */ int step_through_delay = gdbarch_single_step_through_delay (gdbarch, frame); if (step_through_delay) infrun_debug_printf ("step through delay"); if (ecs->event_thread->control.step_range_end == 0 && step_through_delay) { /* The user issued a continue when stopped at a breakpoint. Set up for another trap and get out of here. */ ecs->event_thread->stepping_over_breakpoint = 1; keep_going (ecs); return; } else if (step_through_delay) { /* The user issued a step when stopped at a breakpoint. Maybe we should stop, maybe we should not - the delay slot *might* correspond to a line of source. In any case, don't decide that here, just set ecs->stepping_over_breakpoint, making sure we single-step again before breakpoints are re-inserted. */ ecs->event_thread->stepping_over_breakpoint = 1; } } /* See if there is a breakpoint/watchpoint/catchpoint/etc. that handles this event. */ ecs->event_thread->control.stop_bpstat = bpstat_stop_status (get_current_regcache ()->aspace (), ecs->event_thread->stop_pc (), ecs->event_thread, ecs->ws, stop_chain); /* Following in case break condition called a function. */ stop_print_frame = true; /* This is where we handle "moribund" watchpoints. Unlike software breakpoints traps, hardware watchpoint traps are always distinguishable from random traps. If no high-level watchpoint is associated with the reported stop data address anymore, then the bpstat does not explain the signal --- simply make sure to ignore it if `stopped_by_watchpoint' is set. */ if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && !bpstat_explains_signal (ecs->event_thread->control.stop_bpstat, GDB_SIGNAL_TRAP) && stopped_by_watchpoint) { infrun_debug_printf ("no user watchpoint explains watchpoint SIGTRAP, " "ignoring"); } /* NOTE: cagney/2003-03-29: These checks for a random signal at one stage in the past included checks for an inferior function call's call dummy's return breakpoint. The original comment, that went with the test, read: ``End of a stack dummy. Some systems (e.g. Sony news) give another signal besides SIGTRAP, so check here as well as above.'' If someone ever tries to get call dummys on a non-executable stack to work (where the target would stop with something like a SIGSEGV), then those tests might need to be re-instated. Given, however, that the tests were only enabled when momentary breakpoints were not being used, I suspect that it won't be the case. NOTE: kettenis/2004-02-05: Indeed such checks don't seem to be necessary for call dummies on a non-executable stack on SPARC. */ /* See if the breakpoints module can explain the signal. */ random_signal = !bpstat_explains_signal (ecs->event_thread->control.stop_bpstat, ecs->event_thread->stop_signal ()); /* Maybe this was a trap for a software breakpoint that has since been removed. */ if (random_signal && target_stopped_by_sw_breakpoint ()) { if (gdbarch_program_breakpoint_here_p (gdbarch, ecs->event_thread->stop_pc ())) { struct regcache *regcache; int decr_pc; /* Re-adjust PC to what the program would see if GDB was not debugging it. */ regcache = get_thread_regcache (ecs->event_thread); decr_pc = gdbarch_decr_pc_after_break (gdbarch); if (decr_pc != 0) { gdb::optional<scoped_restore_tmpl<int>> restore_operation_disable; if (record_full_is_used ()) restore_operation_disable.emplace (record_full_gdb_operation_disable_set ()); regcache_write_pc (regcache, ecs->event_thread->stop_pc () + decr_pc); } } else { /* A delayed software breakpoint event. Ignore the trap. */ infrun_debug_printf ("delayed software breakpoint trap, ignoring"); random_signal = 0; } } /* Maybe this was a trap for a hardware breakpoint/watchpoint that has since been removed. */ if (random_signal && target_stopped_by_hw_breakpoint ()) { /* A delayed hardware breakpoint event. Ignore the trap. */ infrun_debug_printf ("delayed hardware breakpoint/watchpoint " "trap, ignoring"); random_signal = 0; } /* If not, perhaps stepping/nexting can. */ if (random_signal) random_signal = !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && currently_stepping (ecs->event_thread)); /* Perhaps the thread hit a single-step breakpoint of _another_ thread. Single-step breakpoints are transparent to the breakpoints module. */ if (random_signal) random_signal = !ecs->hit_singlestep_breakpoint; /* No? Perhaps we got a moribund watchpoint. */ if (random_signal) random_signal = !stopped_by_watchpoint; /* Always stop if the user explicitly requested this thread to remain stopped. */ if (ecs->event_thread->stop_requested) { random_signal = 1; infrun_debug_printf ("user-requested stop"); } /* For the program's own signals, act according to the signal handling tables. */ if (random_signal) { /* Signal not for debugging purposes. */ enum gdb_signal stop_signal = ecs->event_thread->stop_signal (); infrun_debug_printf ("random signal (%s)", gdb_signal_to_symbol_string (stop_signal)); stopped_by_random_signal = 1; /* Always stop on signals if we're either just gaining control of the program, or the user explicitly requested this thread to remain stopped. */ if (stop_soon != NO_STOP_QUIETLY || ecs->event_thread->stop_requested || signal_stop_state (ecs->event_thread->stop_signal ())) { stop_waiting (ecs); return; } /* Notify observers the signal has "handle print" set. Note we returned early above if stopping; normal_stop handles the printing in that case. */ if (signal_print[ecs->event_thread->stop_signal ()]) { /* The signal table tells us to print about this signal. */ target_terminal::ours_for_output (); gdb::observers::signal_received.notify (ecs->event_thread->stop_signal ()); target_terminal::inferior (); } /* Clear the signal if it should not be passed. */ if (signal_program[ecs->event_thread->stop_signal ()] == 0) ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); if (ecs->event_thread->prev_pc == ecs->event_thread->stop_pc () && ecs->event_thread->control.trap_expected && ecs->event_thread->control.step_resume_breakpoint == nullptr) { /* We were just starting a new sequence, attempting to single-step off of a breakpoint and expecting a SIGTRAP. Instead this signal arrives. This signal will take us out of the stepping range so GDB needs to remember to, when the signal handler returns, resume stepping off that breakpoint. */ /* To simplify things, "continue" is forced to use the same code paths as single-step - set a breakpoint at the signal return address and then, once hit, step off that breakpoint. */ infrun_debug_printf ("signal arrived while stepping over breakpoint"); insert_hp_step_resume_breakpoint_at_frame (frame); ecs->event_thread->step_after_step_resume_breakpoint = 1; /* Reset trap_expected to ensure breakpoints are re-inserted. */ ecs->event_thread->control.trap_expected = 0; /* If we were nexting/stepping some other thread, switch to it, so that we don't continue it, losing control. */ if (!switch_back_to_stepped_thread (ecs)) keep_going (ecs); return; } if (ecs->event_thread->stop_signal () != GDB_SIGNAL_0 && (pc_in_thread_step_range (ecs->event_thread->stop_pc (), ecs->event_thread) || ecs->event_thread->control.step_range_end == 1) && (get_stack_frame_id (frame) == ecs->event_thread->control.step_stack_frame_id) && ecs->event_thread->control.step_resume_breakpoint == nullptr) { /* The inferior is about to take a signal that will take it out of the single step range. Set a breakpoint at the current PC (which is presumably where the signal handler will eventually return) and then allow the inferior to run free. Note that this is only needed for a signal delivered while in the single-step range. Nested signals aren't a problem as they eventually all return. */ infrun_debug_printf ("signal may take us out of single-step range"); clear_step_over_info (); insert_hp_step_resume_breakpoint_at_frame (frame); ecs->event_thread->step_after_step_resume_breakpoint = 1; /* Reset trap_expected to ensure breakpoints are re-inserted. */ ecs->event_thread->control.trap_expected = 0; keep_going (ecs); return; } /* Note: step_resume_breakpoint may be non-NULL. This occurs when either there's a nested signal, or when there's a pending signal enabled just as the signal handler returns (leaving the inferior at the step-resume-breakpoint without actually executing it). Either way continue until the breakpoint is really hit. */ if (!switch_back_to_stepped_thread (ecs)) { infrun_debug_printf ("random signal, keep going"); keep_going (ecs); } return; } process_event_stop_test (ecs); } ``` # bpstat_stop_status 在呼叫bpstat_stop_status 又先存了gdbarch,這裡就是要檢查bp是否跟當前程式運行到的位置是否相符 ```c= /* At this point, get hold of the now-current thread's frame. */ frame = get_current_frame (); gdbarch = get_frame_arch (frame); ``` ```c= /* See breakpoint.h. */ bpstat * bpstat_stop_status (const address_space *aspace, CORE_ADDR bp_addr, thread_info *thread, const target_waitstatus &ws, bpstat *stop_chain) { struct breakpoint *b = NULL; /* First item of allocated bpstat's. */ bpstat *bs_head = stop_chain; bpstat *bs; int need_remove_insert; int removed_any; /* First, build the bpstat chain with locations that explain a target stop, while being careful to not set the target running, as that may invalidate locations (in particular watchpoint locations are recreated). Resuming will happen here with breakpoint conditions or watchpoint expressions that include inferior function calls. */ if (bs_head == NULL) bs_head = build_bpstat_chain (aspace, bp_addr, ws); /* A bit of special processing for shlib breakpoints. We need to process solib loading here, so that the lists of loaded and unloaded libraries are correct before we handle "catch load" and "catch unload". */ for (bs = bs_head; bs != NULL; bs = bs->next) { if (bs->breakpoint_at && bs->breakpoint_at->type == bp_shlib_event) { handle_solib_event (); break; } } /* Now go through the locations that caused the target to stop, and check whether we're interested in reporting this stop to higher layers, or whether we should resume the target transparently. */ removed_any = 0; for (bs = bs_head; bs != NULL; bs = bs->next) { if (!bs->stop) continue; b = bs->breakpoint_at; b->check_status (bs); if (bs->stop) { bpstat_check_breakpoint_conditions (bs, thread); if (bs->stop) { ++(b->hit_count); /* We will stop here. */ if (b->disposition == disp_disable) { --(b->enable_count); if (b->enable_count <= 0) b->enable_state = bp_disabled; removed_any = 1; } gdb::observers::breakpoint_modified.notify (b); if (b->silent) bs->print = false; bs->commands = b->commands; if (command_line_is_silent (bs->commands ? bs->commands.get () : NULL)) bs->print = false; b->after_condition_true (bs); } } /* Print nothing for this entry if we don't stop or don't print. */ if (!bs->stop || !bs->print) bs->print_it = print_it_noop; } /* If we aren't stopping, the value of some hardware watchpoint may not have changed, but the intermediate memory locations we are watching may have. Don't bother if we're stopping; this will get done later. */ need_remove_insert = 0; if (! bpstat_causes_stop (bs_head)) for (bs = bs_head; bs != NULL; bs = bs->next) if (!bs->stop && bs->breakpoint_at && is_hardware_watchpoint (bs->breakpoint_at)) { struct watchpoint *w = (struct watchpoint *) bs->breakpoint_at; update_watchpoint (w, false /* don't reparse. */); need_remove_insert = 1; } if (need_remove_insert) update_global_location_list (UGLL_MAY_INSERT); else if (removed_any) update_global_location_list (UGLL_DONT_INSERT); return bs_head; } ``` ```c= /* First, build the bpstat chain with locations that explain a target stop, while being careful to not set the target running, as that may invalidate locations (in particular watchpoint locations are recreated). Resuming will happen here with breakpoint conditions or watchpoint expressions that include inferior function calls. */ if (bs_head == NULL) bs_head = build_bpstat_chain (aspace, bp_addr, ws); /* A bit of special processing for shlib breakpoints. We need to process solib loading here, so that the lists of loaded and unloaded libraries are correct before we handle "catch load" and "catch unload". */ for (bs = bs_head; bs != NULL; bs = bs->next) { if (bs->breakpoint_at && bs->breakpoint_at->type == bp_shlib_event) { handle_solib_event (); break; } ``` 在gdb直接下shardlib會會問你是否要直接下,觸發事件就是在這裡 handle_solib_event (); 那我們先看 build_bpstat_chain (aspace, bp_addr, ws); 也就是gdb會先從所有斷點中,再把 再回想一下 ```C= /* See if there is a breakpoint/watchpoint/catchpoint/etc. that handles this event. */ ecs->event_thread->control.stop_bpstat = bpstat_stop_status (get_current_regcache ()->aspace (), ecs->event_thread->stop_pc (), ecs->event_thread, ecs->ws, stop_chain); ``` 我們把當前程式的Aaddress 都傳進來為的就是要呼叫breakpoint裡面的address做比對,也就是在會先從build_bpstat_chain,撈出所有breakpoint 在bpstat_check_location 做檢查,當然breakpoint有各種不同的type ![](https://i.imgur.com/W5U9cpg.png) watchpoint,breakpoint等等, 反正都是對應到(應該) ```c= int code_breakpoint::breakpoint_hit (const struct bp_location *bl, const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { if (ws.kind () != TARGET_WAITKIND_STOPPED || ws.sig () != GDB_SIGNAL_TRAP) return 0; if (!breakpoint_address_match (bl->pspace->aspace, bl->address, aspace, bp_addr)) return 0; if (overlay_debugging /* unmapped overlay section */ && section_is_overlay (bl->section) && !section_is_mapped (bl->section)) return 0; return 1; } ``` 這邊又回到breakpoint_address_match ```c= /* See breakpoint.h. */ int breakpoint_address_match (const address_space *aspace1, CORE_ADDR addr1, const address_space *aspace2, CORE_ADDR addr2) { return ((gdbarch_has_global_breakpoints (target_gdbarch ()) || aspace1 == aspace2) && addr1 == addr2); } ``` 比較pc addres 和 address_space 是否相符,相符就代表遇到斷點了, ```c= if (!bpstat_check_location (bl, aspace, bp_addr, ws)) continue; /* Come here if it's a watchpoint, or if the break address matches. */ bpstat *bs = new bpstat (bl, &bs_link); /* Alloc a bpstat to explain stop. */ /* Assume we stop. Should we find a watchpoint that is not actually triggered, or if the condition of the breakpoint evaluates as false, we'll reset 'stop' to 0. */ bs->stop = true; bs->print = true; ``` # bpstat_check_location ```c= static bool bpstat_check_location (const struct bp_location *bl, const address_space *aspace, CORE_ADDR bp_addr, const target_waitstatus &ws) { struct breakpoint *b = bl->owner; /* BL is from an existing breakpoint. */ gdb_assert (b != NULL); return b->breakpoint_hit (bl, aspace, bp_addr, ws); } ``` 這樣就會增加到bs_head 節點上了,這樣這單個breakpoint就算生效,也就是該次的SINGAL會存到 ```C= ecs->event_thread->control.stop_bpstat ``` 此時該SINGAL 裡面的某個斷點bs->stop 為TRUE ```C= ecs->event_thread->control.stop_bpstat ``` 設定該SIGNAL 是因為什麼而中斷 # bpstat_explains_signal ```C= if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP && !bpstat_explains_signal (ecs->event_thread->control.stop_bpstat, GDB_SIGNAL_TRAP) && stopped_by_watchpoint) { infrun_debug_printf ("no user watchpoint explains watchpoint SIGTRAP, " "ignoring"); } ``` # bpstat_explains_signal ```C= /* See breakpoint.h. */ bool bpstat_explains_signal (bpstat *bsp, enum gdb_signal sig) { for (; bsp != NULL; bsp = bsp->next) { if (bsp->breakpoint_at == NULL) { /* A moribund location can never explain a signal other than GDB_SIGNAL_TRAP. */ if (sig == GDB_SIGNAL_TRAP) return true; } else { if (bsp->breakpoint_at->explains_signal (sig)) return true; } } return false; } ``` # process_event_stop_test 然後可以看到handle_signal_stop最後CALL呼叫process_event_stop_test就是對後續SINGAL處理 ```c= static void process_event_stop_test (struct execution_control_state *ecs) { struct symtab_and_line stop_pc_sal; frame_info_ptr frame; struct gdbarch *gdbarch; CORE_ADDR jmp_buf_pc; struct bpstat_what what; /* Handle cases caused by hitting a breakpoint. */ frame = get_current_frame (); gdbarch = get_frame_arch (frame); what = bpstat_what (ecs->event_thread->control.stop_bpstat); ``` 可以看到 what = bpstat_what (ecs->event_thread->control.stop_bpstat); bpstat_what又回到剛剛的breakpoint來決定到底是什麼停下來 # back to breakpoint ```c= /* Prepare WHAT final decision for infrun. */ /* Decide what infrun needs to do with this bpstat. */ struct bpstat_what bpstat_what (bpstat *bs_head) { struct bpstat_what retval; bpstat *bs; retval.main_action = BPSTAT_WHAT_KEEP_CHECKING; retval.call_dummy = STOP_NONE; retval.is_longjmp = false; for (bs = bs_head; bs != NULL; bs = bs->next) { /* Extract this BS's action. After processing each BS, we check if its action overrides all we've seem so far. */ enum bpstat_what_main_action this_action = BPSTAT_WHAT_KEEP_CHECKING; enum bptype bptype; if (bs->breakpoint_at == NULL) { /* I suspect this can happen if it was a momentary breakpoint which has since been deleted. */ bptype = bp_none; } else bptype = bs->breakpoint_at->type; switch (bptype) { case bp_none: break; case bp_breakpoint: case bp_hardware_breakpoint: case bp_single_step: case bp_until: case bp_finish: case bp_shlib_event: if (bs->stop) { if (bs->print) this_action = BPSTAT_WHAT_STOP_NOISY; else this_action = BPSTAT_WHAT_STOP_SILENT; } else this_action = BPSTAT_WHAT_SINGLE; break; case bp_watchpoint: case bp_hardware_watchpoint: case bp_read_watchpoint: case bp_access_watchpoint: if (bs->stop) { if (bs->print) this_action = BPSTAT_WHAT_STOP_NOISY; else this_action = BPSTAT_WHAT_STOP_SILENT; } else { /* There was a watchpoint, but we're not stopping. This requires no further action. */ } break; case bp_longjmp: case bp_longjmp_call_dummy: case bp_exception: if (bs->stop) { this_action = BPSTAT_WHAT_SET_LONGJMP_RESUME; retval.is_longjmp = bptype != bp_exception; } else this_action = BPSTAT_WHAT_SINGLE; break; case bp_longjmp_resume: case bp_exception_resume: if (bs->stop) { this_action = BPSTAT_WHAT_CLEAR_LONGJMP_RESUME; retval.is_longjmp = bptype == bp_longjmp_resume; } else this_action = BPSTAT_WHAT_SINGLE; break; case bp_step_resume: if (bs->stop) this_action = BPSTAT_WHAT_STEP_RESUME; else { /* It is for the wrong frame. */ this_action = BPSTAT_WHAT_SINGLE; } break; case bp_hp_step_resume: if (bs->stop) this_action = BPSTAT_WHAT_HP_STEP_RESUME; else { /* It is for the wrong frame. */ this_action = BPSTAT_WHAT_SINGLE; } break; case bp_watchpoint_scope: case bp_thread_event: case bp_overlay_event: case bp_longjmp_master: case bp_std_terminate_master: case bp_exception_master: this_action = BPSTAT_WHAT_SINGLE; break; case bp_catchpoint: if (bs->stop) { if (bs->print) this_action = BPSTAT_WHAT_STOP_NOISY; else this_action = BPSTAT_WHAT_STOP_SILENT; } else { /* Some catchpoints are implemented with breakpoints. For those, we need to step over the breakpoint. */ if (bs->bp_location_at->loc_type == bp_loc_software_breakpoint || bs->bp_location_at->loc_type == bp_loc_hardware_breakpoint) this_action = BPSTAT_WHAT_SINGLE; } break; case bp_jit_event: this_action = BPSTAT_WHAT_SINGLE; break; case bp_call_dummy: /* Make sure the action is stop (silent or noisy), so infrun.c pops the dummy frame. */ retval.call_dummy = STOP_STACK_DUMMY; this_action = BPSTAT_WHAT_STOP_SILENT; break; case bp_std_terminate: /* Make sure the action is stop (silent or noisy), so infrun.c pops the dummy frame. */ retval.call_dummy = STOP_STD_TERMINATE; this_action = BPSTAT_WHAT_STOP_SILENT; break; case bp_tracepoint: case bp_fast_tracepoint: case bp_static_tracepoint: case bp_static_marker_tracepoint: /* Tracepoint hits should not be reported back to GDB, and if one got through somehow, it should have been filtered out already. */ internal_error (_("bpstat_what: tracepoint encountered")); break; case bp_gnu_ifunc_resolver: /* Step over it (and insert bp_gnu_ifunc_resolver_return). */ this_action = BPSTAT_WHAT_SINGLE; break; case bp_gnu_ifunc_resolver_return: /* The breakpoint will be removed, execution will restart from the PC of the former breakpoint. */ this_action = BPSTAT_WHAT_KEEP_CHECKING; break; case bp_dprintf: if (bs->stop) this_action = BPSTAT_WHAT_STOP_SILENT; else this_action = BPSTAT_WHAT_SINGLE; break; default: internal_error (_("bpstat_what: unhandled bptype %d"), (int) bptype); } retval.main_action = std::max (retval.main_action, this_action); } return retval; } ``` 可以看到gdb 一進來就是對所有的breakpoint做掃描 還沒開始檢查就是 bpstat_what_main_action this_action = BPSTAT_WHAT_KEEP_CHECKING; ```c= for (bs = bs_head; bs != NULL; bs = bs->next) { /* Extract this BS's action. After processing each BS, we check if its action overrides all we've seem so far. */ enum bpstat_what_main_action this_action = BPSTAT_WHAT_KEEP_CHECKING; enum bptype bptype; if (bs->breakpoint_at == NULL) { /* I suspect this can happen if it was a momentary breakpoint which has since been deleted. */ bptype = bp_none; } else bptype = bs->breakpoint_at->type; switch (bptype) { case bp_none: break; case bp_breakpoint: case bp_hardware_breakpoint: case bp_single_step: case bp_until: case bp_finish: case bp_shlib_event: if (bs->stop) { if (bs->print) this_action = BPSTAT_WHAT_STOP_NOISY; else this_action = BPSTAT_WHAT_STOP_SILENT; } else this_action = BPSTAT_WHAT_SINGLE; break; ``` 那可以看到gdb可以使用slinet功能來決定要不要輸出一些ouput ```c= case bp_breakpoint: case bp_hardware_breakpoint: case bp_single_step: case bp_until: case bp_finish: case bp_shlib_event: if (bs->stop) { if (bs->print) this_action = BPSTAT_WHAT_STOP_NOISY; else this_action = BPSTAT_WHAT_STOP_SILENT; } else this_action = BPSTAT_WHAT_SINGLE; break; ``` 假設我們剛剛斷點成功觸發也就代表bs->stop = TRUE 也就是this_action = BPSTAT_WHAT_SINGLE 回到process_event_stop_test case BPSTAT_WHAT_SINGLE ```c= case BPSTAT_WHAT_SINGLE: infrun_debug_printf ("BPSTAT_WHAT_SINGLE"); ecs->event_thread->stepping_over_breakpoint = 1; /* Still need to check other stuff, at least the case where we are stepping and step out of the right range. */ break; ``` 可以發現後面就是不斷的在對single事件做細分, keep_going 在呼叫keep_going_pass_signal ```c= /* Called when we should continue running the inferior, because the current event doesn't cause a user visible stop. This does the resuming part; waiting for the next event is done elsewhere. */ static void keep_going (struct execution_control_state *ecs) { if (ecs->event_thread->control.trap_expected && ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP) ecs->event_thread->control.trap_expected = 0; if (!signal_program[ecs->event_thread->stop_signal ()]) ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); keep_going_pass_signal (ecs); } ``` keep_going_pass_signal ```c= /* Like keep_going, but passes the signal to the inferior, even if the signal is set to nopass. */ static void keep_going_pass_signal (struct execution_control_state *ecs) { gdb_assert (ecs->event_thread->ptid == inferior_ptid); gdb_assert (!ecs->event_thread->resumed ()); /* Save the pc before execution, to compare with pc after stop. */ ecs->event_thread->prev_pc = regcache_read_pc_protected (get_thread_regcache (ecs->event_thread)); if (ecs->event_thread->control.trap_expected) { struct thread_info *tp = ecs->event_thread; infrun_debug_printf ("%s has trap_expected set, " "resuming to collect trap", tp->ptid.to_string ().c_str ()); /* We haven't yet gotten our trap, and either: intercepted a non-signal event (e.g., a fork); or took a signal which we are supposed to pass through to the inferior. Simply continue. */ resume (ecs->event_thread->stop_signal ()); } else if (step_over_info_valid_p ()) { /* Another thread is stepping over a breakpoint in-line. If this thread needs a step-over too, queue the request. In either case, this resume must be deferred for later. */ struct thread_info *tp = ecs->event_thread; if (ecs->hit_singlestep_breakpoint || thread_still_needs_step_over (tp)) { infrun_debug_printf ("step-over already in progress: " "step-over for %s deferred", tp->ptid.to_string ().c_str ()); global_thread_step_over_chain_enqueue (tp); } else infrun_debug_printf ("step-over in progress: resume of %s deferred", tp->ptid.to_string ().c_str ()); } else { struct regcache *regcache = get_current_regcache (); int remove_bp; int remove_wps; step_over_what step_what; /* Either the trap was not expected, but we are continuing anyway (if we got a signal, the user asked it be passed to the child) -- or -- We got our expected trap, but decided we should resume from it. We're going to run this baby now! Note that insert_breakpoints won't try to re-insert already inserted breakpoints. Therefore, we don't care if breakpoints were already inserted, or not. */ /* If we need to step over a breakpoint, and we're not using displaced stepping to do so, insert all breakpoints (watchpoints, etc.) but the one we're stepping over, step one instruction, and then re-insert the breakpoint when that step is finished. */ step_what = thread_still_needs_step_over (ecs->event_thread); remove_bp = (ecs->hit_singlestep_breakpoint || (step_what & STEP_OVER_BREAKPOINT)); remove_wps = (step_what & STEP_OVER_WATCHPOINT); /* We can't use displaced stepping if we need to step past a watchpoint. The instruction copied to the scratch pad would still trigger the watchpoint. */ if (remove_bp && (remove_wps || !use_displaced_stepping (ecs->event_thread))) { set_step_over_info (regcache->aspace (), regcache_read_pc (regcache), remove_wps, ecs->event_thread->global_num); } else if (remove_wps) set_step_over_info (nullptr, 0, remove_wps, -1); /* If we now need to do an in-line step-over, we need to stop all other threads. Note this must be done before insert_breakpoints below, because that removes the breakpoint we're about to step over, otherwise other threads could miss it. */ if (step_over_info_valid_p () && target_is_non_stop_p ()) stop_all_threads ("starting in-line step-over"); /* Stop stepping if inserting breakpoints fails. */ try { insert_breakpoints (); } catch (const gdb_exception_error &e) { exception_print (gdb_stderr, e); stop_waiting (ecs); clear_step_over_info (); return; } ecs->event_thread->control.trap_expected = (remove_bp || remove_wps); resume (ecs->event_thread->stop_signal ()); } prepare_to_wait (ecs); } ``` # prepare_to_wait 這邊就算確定整個inferior是要被中斷的ecs->wait_some_more=1 大概意思是這個中斷確實是因為已確定因素而中斷. ```c= /* This function normally comes after a resume, before handle_inferior_event exits. It takes care of any last bits of housekeeping, and sets the all-important wait_some_more flag. */ static void prepare_to_wait (struct execution_control_state *ecs) { infrun_debug_printf ("prepare_to_wait"); ecs->wait_some_more = 1; /* If the target can't async, emulate it by marking the infrun event handler such that as soon as we get back to the event-loop, we immediately end up in fetch_inferior_event again calling target_wait. */ if (!target_can_async_p ()) mark_infrun_async_event_handler (); } ``` # insert_breakpoints (); ```C= /* Make sure all breakpoints are inserted in inferior. Throws exception on any error. A breakpoint that is already inserted won't be inserted again, so calling this function twice is safe. */ void insert_breakpoints (void) { for (breakpoint *bpt : all_breakpoints ()) if (is_hardware_watchpoint (bpt)) { struct watchpoint *w = (struct watchpoint *) bpt; update_watchpoint (w, false /* don't reparse. */); } /* Updating watchpoints creates new locations, so update the global location list. Explicitly tell ugll to insert locations and ignore breakpoints_always_inserted_mode. Also, update_global_location_list tries to "upgrade" software breakpoints to hardware breakpoints to handle "set breakpoint auto-hw", so we need to call it even if we don't have new locations. */ update_global_location_list (UGLL_INSERT); } ``` # handle_inferior_event 總而言之,會有一個地方在控管所有的事件 在這邊會創建被監控的 ```C= If a child inferior was created by infrun while following the fork (CHILD_INF is non-nullptr), push this target on CHILD_INF's target stack and add an initial thread with ptid CHILD_PTID. */ void follow_fork (inferior *child_inf, ptid_t child_ptid, target_waitkind fork_kind, bool follow_child, bool detach_on_fork) override; ``` 監控fork出來的process 查看現在的process什麼狀態 ```C= bool should_resume = follow_fork (); /* Note that one of these may be an invalid pointer, depending on detach_fork. */ thread_info *parent = ecs->event_thread; thread_info *child = find_thread_ptid (targ, ecs->ws.child_ptid ()); case TARGET_WAITKIND_STOPPED: handle_signal_stop (ecs); return; ``` ```C= /* Given an execution control state that has been freshly filled in by an event from the inferior, figure out what it means and take appropriate action. The alternatives are: 1) stop_waiting and return; to really stop and return to the debugger. 2) keep_going and return; to wait for the next event (set ecs->event_thread->stepping_over_breakpoint to 1 to single step once). */ static void handle_inferior_event (struct execution_control_state *ecs) { /* Make sure that all temporary struct value objects that were created during the handling of the event get deleted at the end. */ scoped_value_mark free_values; infrun_debug_printf ("%s", ecs->ws.to_string ().c_str ()); if (ecs->ws.kind () == TARGET_WAITKIND_IGNORE) { /* We had an event in the inferior, but we are not interested in handling it at this level. The lower layers have already done what needs to be done, if anything. One of the possible circumstances for this is when the inferior produces output for the console. The inferior has not stopped, and we are ignoring the event. Another possible circumstance is any event which the lower level knows will be reported multiple times without an intervening resume. */ prepare_to_wait (ecs); return; } if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED) { prepare_to_wait (ecs); return; } if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED && handle_no_resumed (ecs)) return; /* Cache the last target/ptid/waitstatus. */ set_last_target_status (ecs->target, ecs->ptid, ecs->ws); /* Always clear state belonging to the previous time we stopped. */ stop_stack_dummy = STOP_NONE; if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED) { /* No unwaited-for children left. IOW, all resumed children have exited. */ stop_print_frame = false; stop_waiting (ecs); return; } if (ecs->ws.kind () != TARGET_WAITKIND_EXITED && ecs->ws.kind () != TARGET_WAITKIND_SIGNALLED) { ecs->event_thread = find_thread_ptid (ecs->target, ecs->ptid); /* If it's a new thread, add it to the thread database. */ if (ecs->event_thread == nullptr) ecs->event_thread = add_thread (ecs->target, ecs->ptid); /* Disable range stepping. If the next step request could use a range, this will be end up re-enabled then. */ ecs->event_thread->control.may_range_step = 0; } /* Dependent on valid ECS->EVENT_THREAD. */ adjust_pc_after_break (ecs->event_thread, ecs->ws); /* Dependent on the current PC value modified by adjust_pc_after_break. */ reinit_frame_cache (); breakpoint_retire_moribund (); /* First, distinguish signals caused by the debugger from signals that have to do with the program's own actions. Note that breakpoint insns may cause SIGTRAP or SIGILL or SIGEMT, depending on the operating system version. Here we detect when a SIGILL or SIGEMT is really a breakpoint and change it to SIGTRAP. We do something similar for SIGSEGV, since a SIGSEGV will be generated when we're trying to execute a breakpoint instruction on a non-executable stack. This happens for call dummy breakpoints for architectures like SPARC that place call dummies on the stack. */ if (ecs->ws.kind () == TARGET_WAITKIND_STOPPED && (ecs->ws.sig () == GDB_SIGNAL_ILL || ecs->ws.sig () == GDB_SIGNAL_SEGV || ecs->ws.sig () == GDB_SIGNAL_EMT)) { struct regcache *regcache = get_thread_regcache (ecs->event_thread); if (breakpoint_inserted_here_p (regcache->aspace (), regcache_read_pc (regcache))) { infrun_debug_printf ("Treating signal as SIGTRAP"); ecs->ws.set_stopped (GDB_SIGNAL_TRAP); } } mark_non_executing_threads (ecs->target, ecs->ptid, ecs->ws); switch (ecs->ws.kind ()) { case TARGET_WAITKIND_LOADED: { context_switch (ecs); /* Ignore gracefully during startup of the inferior, as it might be the shell which has just loaded some objects, otherwise add the symbols for the newly loaded objects. Also ignore at the beginning of an attach or remote session; we will query the full list of libraries once the connection is established. */ stop_kind stop_soon = get_inferior_stop_soon (ecs); if (stop_soon == NO_STOP_QUIETLY) { struct regcache *regcache; regcache = get_thread_regcache (ecs->event_thread); handle_solib_event (); ecs->event_thread->set_stop_pc (regcache_read_pc (regcache)); ecs->event_thread->control.stop_bpstat = bpstat_stop_status_nowatch (regcache->aspace (), ecs->event_thread->stop_pc (), ecs->event_thread, ecs->ws); if (handle_stop_requested (ecs)) return; if (bpstat_causes_stop (ecs->event_thread->control.stop_bpstat)) { /* A catchpoint triggered. */ process_event_stop_test (ecs); return; } /* If requested, stop when the dynamic linker notifies gdb of events. This allows the user to get control and place breakpoints in initializer routines for dynamically loaded objects (among other things). */ ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); if (stop_on_solib_events) { /* Make sure we print "Stopped due to solib-event" in normal_stop. */ stop_print_frame = true; stop_waiting (ecs); return; } } /* If we are skipping through a shell, or through shared library loading that we aren't interested in, resume the program. If we're running the program normally, also resume. */ if (stop_soon == STOP_QUIETLY || stop_soon == NO_STOP_QUIETLY) { /* Loading of shared libraries might have changed breakpoint addresses. Make sure new breakpoints are inserted. */ if (stop_soon == NO_STOP_QUIETLY) insert_breakpoints (); resume (GDB_SIGNAL_0); prepare_to_wait (ecs); return; } /* But stop if we're attaching or setting up a remote connection. */ if (stop_soon == STOP_QUIETLY_NO_SIGSTOP || stop_soon == STOP_QUIETLY_REMOTE) { infrun_debug_printf ("quietly stopped"); stop_waiting (ecs); return; } internal_error (_("unhandled stop_soon: %d"), (int) stop_soon); } case TARGET_WAITKIND_SPURIOUS: if (handle_stop_requested (ecs)) return; context_switch (ecs); resume (GDB_SIGNAL_0); prepare_to_wait (ecs); return; case TARGET_WAITKIND_THREAD_CREATED: if (handle_stop_requested (ecs)) return; context_switch (ecs); if (!switch_back_to_stepped_thread (ecs)) keep_going (ecs); return; case TARGET_WAITKIND_EXITED: case TARGET_WAITKIND_SIGNALLED: { /* Depending on the system, ecs->ptid may point to a thread or to a process. On some targets, target_mourn_inferior may need to have access to the just-exited thread. That is the case of GNU/Linux's "checkpoint" support, for example. Call the switch_to_xxx routine as appropriate. */ thread_info *thr = find_thread_ptid (ecs->target, ecs->ptid); if (thr != nullptr) switch_to_thread (thr); else { inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid); switch_to_inferior_no_thread (inf); } } handle_vfork_child_exec_or_exit (0); target_terminal::ours (); /* Must do this before mourn anyway. */ /* Clearing any previous state of convenience variables. */ clear_exit_convenience_vars (); if (ecs->ws.kind () == TARGET_WAITKIND_EXITED) { /* Record the exit code in the convenience variable $_exitcode, so that the user can inspect this again later. */ set_internalvar_integer (lookup_internalvar ("_exitcode"), (LONGEST) ecs->ws.exit_status ()); /* Also record this in the inferior itself. */ current_inferior ()->has_exit_code = true; current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status (); /* Support the --return-child-result option. */ return_child_result_value = ecs->ws.exit_status (); gdb::observers::exited.notify (ecs->ws.exit_status ()); } else { struct gdbarch *gdbarch = current_inferior ()->gdbarch; if (gdbarch_gdb_signal_to_target_p (gdbarch)) { /* Set the value of the internal variable $_exitsignal, which holds the signal uncaught by the inferior. */ set_internalvar_integer (lookup_internalvar ("_exitsignal"), gdbarch_gdb_signal_to_target (gdbarch, ecs->ws.sig ())); } else { /* We don't have access to the target's method used for converting between signal numbers (GDB's internal representation <-> target's representation). Therefore, we cannot do a good job at displaying this information to the user. It's better to just warn her about it (if infrun debugging is enabled), and give up. */ infrun_debug_printf ("Cannot fill $_exitsignal with the correct " "signal number."); } gdb::observers::signal_exited.notify (ecs->ws.sig ()); } gdb_flush (gdb_stdout); target_mourn_inferior (inferior_ptid); stop_print_frame = false; stop_waiting (ecs); return; case TARGET_WAITKIND_FORKED: case TARGET_WAITKIND_VFORKED: /* Check whether the inferior is displaced stepping. */ { struct regcache *regcache = get_thread_regcache (ecs->event_thread); struct gdbarch *gdbarch = regcache->arch (); inferior *parent_inf = find_inferior_ptid (ecs->target, ecs->ptid); /* If this is a fork (child gets its own address space copy) and some displaced step buffers were in use at the time of the fork, restore the displaced step buffer bytes in the child process. Architectures which support displaced stepping and fork events must supply an implementation of gdbarch_displaced_step_restore_all_in_ptid. This is not enforced during gdbarch validation to support architectures which support displaced stepping but not forks. */ if (ecs->ws.kind () == TARGET_WAITKIND_FORKED && gdbarch_supports_displaced_stepping (gdbarch)) gdbarch_displaced_step_restore_all_in_ptid (gdbarch, parent_inf, ecs->ws.child_ptid ()); /* If displaced stepping is supported, and thread ecs->ptid is displaced stepping. */ if (displaced_step_in_progress_thread (ecs->event_thread)) { struct regcache *child_regcache; CORE_ADDR parent_pc; /* GDB has got TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED, indicating that the displaced stepping of syscall instruction has been done. Perform cleanup for parent process here. Note that this operation also cleans up the child process for vfork, because their pages are shared. */ displaced_step_finish (ecs->event_thread, GDB_SIGNAL_TRAP); /* Start a new step-over in another thread if there's one that needs it. */ start_step_over (); /* Since the vfork/fork syscall instruction was executed in the scratchpad, the child's PC is also within the scratchpad. Set the child's PC to the parent's PC value, which has already been fixed up. FIXME: we use the parent's aspace here, although we're touching the child, because the child hasn't been added to the inferior list yet at this point. */ child_regcache = get_thread_arch_aspace_regcache (parent_inf->process_target (), ecs->ws.child_ptid (), gdbarch, parent_inf->aspace); /* Read PC value of parent process. */ parent_pc = regcache_read_pc (regcache); displaced_debug_printf ("write child pc from %s to %s", paddress (gdbarch, regcache_read_pc (child_regcache)), paddress (gdbarch, parent_pc)); regcache_write_pc (child_regcache, parent_pc); } } context_switch (ecs); /* Immediately detach breakpoints from the child before there's any chance of letting the user delete breakpoints from the breakpoint lists. If we don't do this early, it's easy to leave left over traps in the child, vis: "break foo; catch fork; c; <fork>; del; c; <child calls foo>". We only follow the fork on the last `continue', and by that time the breakpoint at "foo" is long gone from the breakpoint table. If we vforked, then we don't need to unpatch here, since both parent and child are sharing the same memory pages; we'll need to unpatch at follow/detach time instead to be certain that new breakpoints added between catchpoint hit time and vfork follow are detached. */ if (ecs->ws.kind () != TARGET_WAITKIND_VFORKED) { /* This won't actually modify the breakpoint list, but will physically remove the breakpoints from the child. */ detach_breakpoints (ecs->ws.child_ptid ()); } delete_just_stopped_threads_single_step_breakpoints (); /* In case the event is caught by a catchpoint, remember that the event is to be followed at the next resume of the thread, and not immediately. */ ecs->event_thread->pending_follow = ecs->ws; ecs->event_thread->set_stop_pc (regcache_read_pc (get_thread_regcache (ecs->event_thread))); ecs->event_thread->control.stop_bpstat = bpstat_stop_status_nowatch (get_current_regcache ()->aspace (), ecs->event_thread->stop_pc (), ecs->event_thread, ecs->ws); if (handle_stop_requested (ecs)) return; /* If no catchpoint triggered for this, then keep going. Note that we're interested in knowing the bpstat actually causes a stop, not just if it may explain the signal. Software watchpoints, for example, always appear in the bpstat. */ if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat)) { bool follow_child = (follow_fork_mode_string == follow_fork_mode_child); ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); process_stratum_target *targ = ecs->event_thread->inf->process_target (); bool should_resume = follow_fork (); /* Note that one of these may be an invalid pointer, depending on detach_fork. */ thread_info *parent = ecs->event_thread; thread_info *child = find_thread_ptid (targ, ecs->ws.child_ptid ()); /* At this point, the parent is marked running, and the child is marked stopped. */ /* If not resuming the parent, mark it stopped. */ if (follow_child && !detach_fork && !non_stop && !sched_multi) parent->set_running (false); /* If resuming the child, mark it running. */ if (follow_child || (!detach_fork && (non_stop || sched_multi))) child->set_running (true); /* In non-stop mode, also resume the other branch. */ if (!detach_fork && (non_stop || (sched_multi && target_is_non_stop_p ()))) { if (follow_child) switch_to_thread (parent); else switch_to_thread (child); ecs->event_thread = inferior_thread (); ecs->ptid = inferior_ptid; keep_going (ecs); } if (follow_child) switch_to_thread (child); else switch_to_thread (parent); ecs->event_thread = inferior_thread (); ecs->ptid = inferior_ptid; if (should_resume) { /* Never call switch_back_to_stepped_thread if we are waiting for vfork-done (waiting for an external vfork child to exec or exit). We will resume only the vforking thread for the purpose of collecting the vfork-done event, and we will restart any step once the critical shared address space window is done. */ if ((!follow_child && detach_fork && parent->inf->thread_waiting_for_vfork_done != nullptr) || !switch_back_to_stepped_thread (ecs)) keep_going (ecs); } else stop_waiting (ecs); return; } process_event_stop_test (ecs); return; case TARGET_WAITKIND_VFORK_DONE: /* Done with the shared memory region. Re-insert breakpoints in the parent, and keep going. */ context_switch (ecs); handle_vfork_done (ecs->event_thread); gdb_assert (inferior_thread () == ecs->event_thread); if (handle_stop_requested (ecs)) return; if (!switch_back_to_stepped_thread (ecs)) { gdb_assert (inferior_thread () == ecs->event_thread); /* This also takes care of reinserting breakpoints in the previously locked inferior. */ keep_going (ecs); } return; case TARGET_WAITKIND_EXECD: /* Note we can't read registers yet (the stop_pc), because we don't yet know the inferior's post-exec architecture. 'stop_pc' is explicitly read below instead. */ switch_to_thread_no_regs (ecs->event_thread); /* Do whatever is necessary to the parent branch of the vfork. */ handle_vfork_child_exec_or_exit (1); /* This causes the eventpoints and symbol table to be reset. Must do this now, before trying to determine whether to stop. */ follow_exec (inferior_ptid, ecs->ws.execd_pathname ()); /* In follow_exec we may have deleted the original thread and created a new one. Make sure that the event thread is the execd thread for that case (this is a nop otherwise). */ ecs->event_thread = inferior_thread (); ecs->event_thread->set_stop_pc (regcache_read_pc (get_thread_regcache (ecs->event_thread))); ecs->event_thread->control.stop_bpstat = bpstat_stop_status_nowatch (get_current_regcache ()->aspace (), ecs->event_thread->stop_pc (), ecs->event_thread, ecs->ws); if (handle_stop_requested (ecs)) return; /* If no catchpoint triggered for this, then keep going. */ if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat)) { ecs->event_thread->set_stop_signal (GDB_SIGNAL_0); keep_going (ecs); return; } process_event_stop_test (ecs); return; /* Be careful not to try to gather much state about a thread that's in a syscall. It's frequently a losing proposition. */ case TARGET_WAITKIND_SYSCALL_ENTRY: /* Getting the current syscall number. */ if (handle_syscall_event (ecs) == 0) process_event_stop_test (ecs); return; /* Before examining the threads further, step this thread to get it entirely out of the syscall. (We get notice of the event when the thread is just on the verge of exiting a syscall. Stepping one instruction seems to get it back into user code.) */ case TARGET_WAITKIND_SYSCALL_RETURN: if (handle_syscall_event (ecs) == 0) process_event_stop_test (ecs); return; case TARGET_WAITKIND_STOPPED: handle_signal_stop (ecs); return; case TARGET_WAITKIND_NO_HISTORY: /* Reverse execution: target ran out of history info. */ /* Switch to the stopped thread. */ context_switch (ecs); infrun_debug_printf ("stopped"); delete_just_stopped_threads_single_step_breakpoints (); ecs->event_thread->set_stop_pc (regcache_read_pc (get_thread_regcache (inferior_thread ()))); if (handle_stop_requested (ecs)) return; gdb::observers::no_history.notify (); stop_waiting (ecs); return; } } ``` inferior_event_handler 監控fork出來的chinld process的狀態 如果程式已經停止,也就意味著該inferior 狀態已經暫停了 ```c= case TARGET_WAITKIND_STOPPED: handle_signal_stop (ecs); return; ``` 則處理該ecs 交給handle_signal_stop 來判斷該inferior 停止原因 wait_for_inferior 會一直檢查剛剛程式中有可能觸發的exception,直到 ```c= if (!ecs.wait_some_more) break; ``` # wait_for_inferior ```c= /* Wait for control to return from inferior to debugger. If inferior gets a signal, we may decide to start it up again instead of returning. That is why there is a loop in this function. When this function actually returns it means the inferior should be left stopped and GDB should read more commands. */ static void wait_for_inferior (inferior *inf) { infrun_debug_printf ("wait_for_inferior ()"); SCOPE_EXIT { delete_just_stopped_threads_infrun_breakpoints (); }; /* If an error happens while handling the event, propagate GDB's knowledge of the executing state to the frontend/user running state. */ scoped_finish_thread_state finish_state (inf->process_target (), minus_one_ptid); while (1) { execution_control_state ecs; overlay_cache_invalid = 1; /* Flush target cache before starting to handle each event. Target was running and cache could be stale. This is just a heuristic. Running threads may modify target memory, but we don't get any event. */ target_dcache_invalidate (); ecs.ptid = do_target_wait_1 (inf, minus_one_ptid, &ecs.ws, 0); ecs.target = inf->process_target (); if (debug_infrun) print_target_wait_results (minus_one_ptid, ecs.ptid, ecs.ws); /* Now figure out what to do with the result of the result. */ handle_inferior_event (&ecs); if (!ecs.wait_some_more) break; } stop_all_threads_if_all_stop_mode (); /* No error, don't finish the state yet. */ finish_state.release (); } ``` 再往上追就是就是gdb 透過command 方式 去讀取file ```c= /* Implement the "run" command. Force a stop during program start if requested by RUN_HOW. */ static void run_command_1 (const char *args, int from_tty, enum run_how run_how) { const char *exec_file; struct ui_out *uiout = current_uiout; struct target_ops *run_target; int async_exec; dont_repeat (); scoped_disable_commit_resumed disable_commit_resumed ("running"); kill_if_already_running (from_tty); init_wait_for_inferior (); clear_breakpoint_hit_counts (); /* Clean up any leftovers from other runs. Some other things from this function should probably be moved into target_pre_inferior. */ target_pre_inferior (from_tty); /* The comment here used to read, "The exec file is re-read every time we do a generic_mourn_inferior, so we just have to worry about the symbol file." The `generic_mourn_inferior' function gets called whenever the program exits. However, suppose the program exits, and *then* the executable file changes? We need to check again here. Since reopen_exec_file doesn't do anything if the timestamp hasn't changed, I don't see the harm. */ reopen_exec_file (); reread_symbols (from_tty); gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec); args = stripped.get (); /* Do validation and preparation before possibly changing anything in the inferior. */ run_target = find_run_target (); ``` 熟悉的attach ```c= /* "attach" command entry point. Takes a program started up outside of gdb and ``attaches'' to it. This stops it cold in its tracks and allows us to start debugging it. */ void attach_command (const char *args, int from_tty) { int async_exec; struct target_ops *attach_target; struct inferior *inferior = current_inferior (); enum attach_post_wait_mode mode; dont_repeat (); /* Not for the faint of heart */ scoped_disable_commit_resumed disable_commit_resumed ("attaching"); if (gdbarch_has_global_solist (target_gdbarch ())) /* Don't complain if all processes share the same symbol space. */ ; else if (target_has_execution ()) { if (query (_("A program is being debugged already. Kill it? "))) target_kill (); else error (_("Not killed.")); } /* Clean up any leftovers from other runs. Some other things from this function should probably be moved into target_pre_inferior. */ target_pre_inferior (from_tty); gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec); args = stripped.get (); attach_target = find_attach_target (); prepare_execution_command (attach_target, async_exec); if (non_stop && !attach_target->supports_non_stop ()) error (_("Cannot attach to this target in non-stop mode")); attach_target->attach (args, from_tty); /* to_attach should push the target, so after this point we shouldn't refer to attach_target again. */ attach_target = nullptr; infrun_debug_show_threads ("immediately after attach", current_inferior ()->non_exited_threads ()); /* Enable async mode if it is supported by the target. */ if (target_can_async_p ()) target_async (true); /* Set up the "saved terminal modes" of the inferior based on what modes we are starting it with. */ target_terminal::init (); /* Install inferior's terminal modes. This may look like a no-op, as we've just saved them above, however, this does more than restore terminal settings: - installs a SIGINT handler that forwards SIGINT to the inferior. Otherwise a Ctrl-C pressed just while waiting for the initial stop would end up as a spurious Quit. - removes stdin from the event loop, which we need if attaching in the foreground, otherwise on targets that report an initial stop on attach (which are most) we'd process input/commands while we're in the event loop waiting for that stop. That is, before the attach continuation runs and the command is really finished. */ target_terminal::inferior (); /* Set up execution context to know that we should return from wait_for_inferior as soon as the target reports a stop. */ init_wait_for_inferior (); ``` 到這邊應該大致知道gdb是怎跑的