gdb调试器之测不准原则
生活随笔
收集整理的這篇文章主要介紹了
gdb调试器之测不准原则
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
gdb調試器之"測不準原則"??
2012-05-07 22:25:30|??分類: gdb源代碼分析 |字號?訂閱
一、測不準原則我大學物理學的不太好,特別是高等物理,這個概念是在很多科普性的讀物中都可以見到,就像”羅素悖論“、哥德爾的”不完備理論“、愛因斯坦的”相對論“等,大家都是一知半解,然后根據這個概念大家自由發揮,所以就有千奇百怪的場景和理解了,最后以訛傳訛,倒也不清楚這個東西原始真正意義,這種現象在很多成語中也經常出現,例如經典的、也是考試的時候出鏡率很高的”差強人意“。
作為無數民間科學家中的一員,我對這個”測不準“的理解就是當你真正觀察它的時候,它和它正常的行為是不同的。這一點可能在很多其他場合也是用,例如……(此處大家可以盡情發揮一下)。或者圍城中胖詩人曹元朗說的”當你以為你理解了我的時候,你就誤解了我“。
二、調試器依賴的手段
當調試器調試一個任務的時候,它同樣會對被調試的任務產生一些微妙的影響,這些影響在一些實時系統中表現的比較突出,特別是那些FIFO類型的實時調度任務,因為當任務被調試的時候,它的很多重要事件都要由內核代勞首先通知給調試器,在調試器發出下一個指示之前,被調試任務(線程)不能繼續運行,這一點對于很多對初始化順序有嚴格要求的系統來說是不能容忍的,所以調試器在很多時候并不是完成的。
對于非實時的系統,調試器同樣可能會影響調度,原因同上,但是現象可能不盡相同。因為非實時系統對于任務的調度順序沒有依賴和假設,它本來就是可以以任意順序運行的,如果需要排序可能使用各種鎖來同步。
調試器主要是通過SIGSTOP來主動要求一個線程暫時冷靜下來,而內核則通過ptrace_stop來強制,大家可以看一下內核在哪些地方調用了這個函數:
static void ptrace_stop(int exit_code, int nostop_code, siginfo_t *info)
{
??? /* Let the debugger run.? */
??? set_current_state(TASK_TRACED);該狀態不可運行,并且不接收信號。
……
if (may_ptrace_stop()) {
??? ??? do_notify_parent_cldstop(current, CLD_TRAPPED);通知父進程,
??? ??? read_unlock(&tasklist_lock);
??? ??? schedule();讓出調度權。
??? } else {
??? ??? /*
??? ??? ?* By the time we got the lock, our tracer went away.
??? ??? ?* Don't stop here.
??? ??? ?*/
??? ??? read_unlock(&tasklist_lock);
??? ??? set_current_state(TASK_RUNNING);
??? ??? current->exit_code = nostop_code;
??? }
}
當進程附加的時候,內核也不拿自己當外人,也是毫不客氣的發送了一個SIGSTOP信號過去。我們知道,很多時候,線程都是信號敏感的,也就是系統調用是可以被信號喚醒的,這明顯會影響系統任務的執行,我們看一下調試器附加的代碼:
sys_ptrace--->>>ptrace_attach
??? force_sig_specific(SIGSTOP, task);
這里向被附加任務發送了一個SIGSTOP信號,之后將會看到,這個調用將會對被調試進程的運行產生影響。
三、附加被調試任務
1、測試代碼
之前的代碼已經看到,它會發送SIGSTOP給被附加線程,我們測試一下最為簡單的read系統調用,測試程序為:
[tsecer@Harry TracerInter]$ cat tracersense.c
#include <fcntl.h>
#include <stdio.h>
int main()
{
??? char buf[10];
??? int readin = read(0,buf,sizeof buf);
??? printf("readin is %d\n",readin);
}
[tsecer@Harry TracerInter]$ gcc tracersense.c -g
[tsecer@Harry TracerInter]$ sleep 1000 | ./a.out
然后到另一個終端中使用調試器附加a.out對應的進程,看它是否會被從read系統調用喚醒。
[root@Harry ~]# cat /proc/18587/status
Name:??? a.out
State:??? S (sleeping)
……
voluntary_ctxt_switches:??? 3 注意這個調度次數,在調試器附加之后,被調試線程的調度次數將會增加
nonvoluntary_ctxt_switches:??? 2
[root@Harry ~]# gdb -p 18587
……
(gdb) shell cat /proc/18587/status
Name:??? a.out
State:??? T (tracing stop)
……
voluntary_ctxt_switches:??? 4 調試器附加之后,被調試任務執行次數加一,說明被調試線程從read系統調用返回了,但是程序沒有退出運行。
nonvoluntary_ctxt_switches:??? 2
(gdb) quit
A debugging session is active.
??? Inferior 1 [process 18587] will be detached.
Quit anyway? (y or n) y
Detaching from program: /home/tsecer/CodeTest/TracerInter/a.out, process 18587
[root@Harry ~]# cat /proc/18587/status
Name:??? a.out
State:??? S (sleeping)
Tgid:??? 18587
……
voluntary_ctxt_switches:??? 5調試器退出附加之后,被調試線程調度次數再次增加。但是奇怪的是被調試任務并沒有從read系統調用返回到用戶態空間(否則進程會直接退出)。
nonvoluntary_ctxt_switches:??? 2
2、管道read被喚醒
linux-2.6.21\fs\pipe.c
pipe_read(struct kiocb *iocb, const struct iovec *_iov, unsigned long nr_segs, loff_t pos)
??? ??? if (signal_pending(current)) {
??? ??? ??? if (!ret)
??? ??? ??? ??? ret = -ERESTARTSYS;
??? ??? ??? break;
??? ??? }
注意這個返回錯誤碼。當調試器收到一個子進程上報的信號之后,如果是自己發送的SIGSTOP,那么會對被調試任務透明的取消這個信號,取消的方法就是通過ptrace的PTRACE_CONT請求實現,看其實現非常簡單linux-2.6.21\arch\i386\kernel\ptrace.c
??? ??? child->exit_code = data;
然后子進程繼續運行,執行信號獲取函數
linux-2.6.21\kernel\signal.c:?? int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka, struct pt_regs *regs, void *cookie)
??? ??? ??? /* Let the debugger run.? */
??? ??? ??? ptrace_stop(signr, signr, info);
??? ??? ??? /* We're back.? Did the debugger cancel the sig?? */
??? ??? ??? signr = current->exit_code;
??? ??? ??? if (signr == 0)
??? ??? ??? ??? continue;
對于我們測試的例子,它剛好滿足這個條件(調試器通過PTRACE_CONT清空了這個信號值),所以直接返回,然后進入信號處理函數
linux-2.6.21\arch\i386\kernel\signal.c:static void fastcall do_signal(struct pt_regs *regs)
if (signr > 0) {不滿足該條件,執行下面分支。
……
??? ??? return;
??? }
??? /* Did we come from a system call? */
??? if (regs->orig_eax >= 0) {
??? ??? /* Restart the system call - no handlers present */
??? ??? switch (regs->eax) {
??? ??? case -ERESTARTNOHAND:
??? ??? case -ERESTARTSYS:
??? ??? case -ERESTARTNOINTR:
??? ??? ??? regs->eax = regs->orig_eax;
??? ??? ??? regs->eip -= 2;
??? ??? ??? break;
??? ??? case -ERESTART_RESTARTBLOCK:
??? ??? ??? regs->eax = __NR_restart_syscall;
??? ??? ??? regs->eip -= 2;這是整個機制的實現核心:將用戶態指針減去兩個字節,也就是386體系結構中linux下系統調用int 0x80指令占用的兩個字節,這樣被中斷的系統調用(read)就可以再次執行而不真正對APP可見這次喚醒。
??? ??? ??? break;
??? ??? }
??? }
我看了一下另一個常用的可以測試的系統調用sys_pause,它被喚醒的時候也是設置
asmlinkage long
sys_pause(void)
{
??? current->state = TASK_INTERRUPTIBLE;
??? schedule();
??? return -ERESTARTNOHAND;
}
所以使用pause測試應用程序可感知喚醒也不行、select也不行,所以這個現象只是作為一個理論存在,但是工程中應該比價少出現的情況,暫且不說。
四、對于不可喚醒睡眠任務的附加失敗例子
[tsecer@Harry TracerInter]$ cat NonInt.c
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
??? pid_t foker;
??? if (0 == (foker = vfork())) 執行vfork,從而使父進程進入不可喚醒休眠。
??? {
??? ??? sleep (1000);
??? }
??? printf("Father side This sentense should never been seen\n");
}
[tsecer@Harry TracerInter]$ gcc NonInt.c -o NonInt.c.exe -g
[tsecer@Harry TracerInter]$ ./NonInt.c.exe
另一個終端中調試器附加父進程:
[root@Harry ~]# ps aux
……
tsecer?? 18841? 0.0? 0.0?? 1740?? 272 pts/0??? D+?? 22:17?? 0:00 ./NonInt.c.exe
tsecer?? 18842? 0.0? 0.0?? 1740?? 272 pts/0??? S+?? 22:17?? 0:00 ./NonInt.c.exe
root???? 18843? 1.0? 0.0?? 4688?? 992 pts/6??? R+?? 22:17?? 0:00 ps aux
[root@Harry ~]# cat /proc/18841/status
Name:??? NonInt.c.exe
State:??? D (disk sleep)
Tgid:??? 18841
Pid:??? 18841
PPid:??? 12127
……
[root@Harry ~]# gdb -p 18841 附加父進程,
GNU gdb (GDB) Fedora (7.0-3.fc12)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.? Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Attaching to process 18841
這個顯示將會一直持續,也就是說調試器將會一直無法從這里返回,這說明調試器沒有收到內核通知的子進程收到SIGSTOP的事件,調試器在此一直等待。
五、sum up
這里沒有分析gdb的實現代碼,只是結合了內核的相關接口猜測和觀察了一下gdb的執行原理,純粹是探討性內容,可能實際意義不大。
總結
以上是生活随笔為你收集整理的gdb调试器之测不准原则的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python积木式编程_TurnipBi
- 下一篇: Blur Multiple Images