[GSoC weekly report#6]How gdb get inferior's signal?

This week I have found out how gdb get inferior's signal.

The magic is the TRACED flags!

The first instruction a inferior will execute after  the first GDB run command is at 0x24f0 where is in /lib/ld.so _start() (macro RTLD_START defined in [glibc]/sysdeps/i386/dl_machine.h). Which will load the dynamic library and finally call init1()->_hurd_init()->_hurd_new_proc_init()->_hurdsig_init(). It will initialize the signal thread and let it listening on the inferior's msgport. The ptrace_me call will set the _hurdsig_traced variable (BTW, the gdb also will explicit set the _hurdsig_traced by msg_set_init_int() in if_set_traced()).

If _hurdsig_traced is set, whenever the inferior get any signal it will stop itself  (by call proc_dostop) and call proc_mark_stop to notify its father task by send_sigal(SIGCHLD) or by pthread_cond_broadcast() (ps: I am not clear about this function). [glibc/hurd/hurdsig.c  post_signal(): line754]  When the inferior stop himself, the S_proc_wait() will returned[1](GDB has called this before),  and send a reply message with msgID 24120 to GDB's event port. When GDB come back from the mach_msg(), it has known all things happened on inferior contains the signal of course. 

These is exist an exception, GDB can post a signal to the inferior by command signal. This command will use a so called msg_sig_post_untraced() RPC, then the signal thread will not pass the untraced signal to GDB back.

[1]the manual page of wait
wait, waitpid, waitid - wait for process to change state.  All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal.

[GSoC weekly report#5]begin coding on gdbserver

With the help of my mentor and Richard Braun, My code demo  HDebugger  can continue from a breakpointer.

After add a thread_abort()  the inferior can resume from breakpoint. Thread_abort() is to clean up the waiting event on specific thread. In the practice I have also got clear about the asynchronous RPC.

The HDebugger now can insert breakpoint, access inferior's memory and modify registers thus haven't implemented the interactive interface yet. For the incoming mid-evaluate, I don't plan to continue the HDebugger, I will begin to code gdbserver.

BTW, I haven't understand the msg_sig_post() call, how is the signal delivered? As gdb is listening on the inferior's exception port, then gdb post a signal to that port. That says: gdb post a message to herself, and handle it. Seems no use, but the msg_sig_post() is essential when gdb resume a inferior. So there must be wrong in my understand.

 

[GSoC weekly report#4]can't resume from a breakpoint

This is my code of HDebugger under GNU/Hurd.  The mainly problem is that it can set a breakpoint but can't resume from it.

The inferior program is just like a hello_world but contains two printf() call. Firstly, I set a breakpoint at the second printf() call, the address if find in the output by objdump -d.

Now, I can get the exception message (which msg.id==2400) from the mach kernel when the inferior hit the breakpoint. When get the message, I recovery the breakpoint of the original code, and set the thread_state by minus the eip register . Then post a signal_0 to it. At last I call thread_resume() try to continue inferior. But I got nothing, the thread is just keeping hang. If I use gdb to attach the inferior when I got the exception message, I can continue the inferior by type "continue command" in gdb' shell.

Answer to  last week:

  1. If  I don’t call proc_wait_request()first in my debugger demo, the mach_msg() call  will never return. This is because the inferior is suspended after execl(), so nobody will send message to us if we don't do a request initiatively.
  2. Is  there exist any specific running sequence between father and child task after fork()? By default, after fork(), the father process run first, so we have a chance to see the execl() to destroy the old threads in child process.
  3. How execl() go? It will destroy the two old threads(main thread and signal thread) forked from the parent process.  Then create a new main thread to run the execute file in execl() parameter. The funny thing is when to create the new signal thread? Never create the signal thread unless an exception appears(NO! The signal thread is initialize early and loop listening on the msgport). This is also the answer to question 4.
Next week goal:
Finish HDebugger and begin to code the gdbserver!

 

[GSoC weekly report#3]dig into the gnu_nat.c

This week I have digged into the GDB source code. I have turn on the gnu_debug_flags in gnu_nat.c, and get about 500 lines output when debugging a hello_world program. Then I try to get clear about the detail of GNU/Hurd follow the output step by step. But I have got a lot of puzzles.

  1. What is the proc server?. And I found that If  I don’t call proc_get_reqeust() proc_wait_request()first in my debugger demo, the mach_msg() call  will never return.
  2. Is  there exist any specific running sequence between father and child task after fork()? And I found the inferior always call the trace_me() in the same time(the trace me printf always in the same line of the output log).
  3. How do the fork() and execl() go? Say there is a father task which contains two threads( one for main thread, and other is the signal thread). After the father call fork(), the child call execl() immediately, how things go? Will execl() destroy the two old threads and then create new ones?
  4. When I set a breakpoint in the hello_world program, I will get five times new thread printf and three threads died printf. If I don't set breakpoint, I only get four times, and two threads died. How to explain this?
  5. How to understand the relationship and difference between structure inf  and structure inferior? I think the inf is low-level presentation of the debugged program, and the other is a high-level presentation. Am I right?
  6. what are the observer_notify_new_thread()  and observer_notify_thread_ptid_changed() do in thread.c?
  7. What are the target_terminal_*() [fork_child.c] like target_terminal_init() and target_ternimal_inferior() do?

Next week goal: solve above questions.

[GSoC weekly report#2]My understand of How exception is thrown and handled in Hurd

This week I have read the <Mach 3 Kernel Principles> and some relate materials. And I have rewritten the MIG use example in <A programmer's guide to the mach user environment>. I put the code in github. Most of the code is stolen from Anand Babu's example.

Now I have known something about the mig basically. I can answer the question of  "What is the S_exception_raise_request()?" .

Firstly, I will describe without debugger stuff. When a task was created, the glibc will created one more thread(so called message thread or signal thread) in the task. The function  _hurdsig_init() [glibc/hurd/hurdsig.c] will initialize the exception port of the other thread in the task, and start the signal thread to listen on all the ports. The signal thread will run the _hurd_msgport_recieve() [glibc/hurd/msgportdemux.c] which invoke the mach_msg_server() [glibc/mach/msgserver.c] with the first parameter of msgport_server() as its demux pointer.

After the initialization, the signal thread is blocked until a exception occur. Whenever an exception occurs(for instance, thread A cause an divide zero exception), the mach kernel will suspend the thread A right now, and the kernel will send a exception message to the thread A's exception port on behalf of A. Meanwhile, our signal thread is already listening on the thread A's exception port. So the mach_msg() will return, and invoke the (*demux)() which is pointer to msgport_server() as described above.

Ok, now we will see the magic of mig. In the last line of msgport_server(), it will return _S_exc_server(). The _S_exc_server() is just an demux who generated by mig  from the mach/include/mach/exc.defs and will call the S_catch_exception_raise() to handle the exception. S_catch_exception_raise() [glibc/hurd/catch-exc.c] deliver the signal by _hurd_internal_post_signal()  [glibc/hurd/hurdsig.c]. Honestly to say, the _hurd_internal_post_signal() function is too complex to understand for me now. I think I will make it clear in the later work. By default, the _hurd_internal_post_signal will invoke the default handler to handle the divide zero exception which just simply terminal the thread A. After this, the whole exception things without debugger is done.

If a task is under the control of gdb, thing will be a little different. Instead of the glibc's _hurd_msgport_recieve(), the gdb will use the the gnu_wait() to listen on the exception port. And then pass the message by (!notify_server()  && !exc_server() && !process_reply_server() && !msg_reply_server()). We only care about exc_server() here, it was generated by the mid from gdb/exc_request.defs like above, and will invoke the S_exception_raise_request() who will handle this exception like the situation in S_catch_exception_raise() above.

This is my understand of how exception is thrown  and be handled.

Again, the next week goal is to finish the debugger demo.