• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-19  評論-21  文章-0  trackbacks-0
            此文只簡單分析發(fā)送信號給用戶程序后,用戶堆棧和內(nèi)核堆棧的變化。沒有分析實時信號,當然整個過程基本一致。很多參考了<情景分析>,所以有些代碼和現(xiàn)在的內(nèi)核可能不同,比如RESTORE_ALL,但大體的機制是類似的。

            1. 一個信號小例子

            hex@Gentoo ~/signal $ cat sigint.c
            #include <stdio.h>
            #include <stdlib.h>
            #include <signal.h>

            void sig_int(int signo)
            {
                printf("hello\n");
            }

            int main()
            {
                if(signal(SIGINT, sig_int) == SIG_ERR){
                    printf("can't catch SIGINT\n");
                    exit(-1);
                }

                for(;;)
                    ;

                return 0;
            }

            2. 用戶堆棧里發(fā)生的故事

            2.1 編譯運行該程序,并設(shè)置斷點在sig_int函數(shù)開頭(0x80482e8),并設(shè)置SIGINT信號的處理方式
            hex@Gentoo ~/signal $ gdb ./sigint
            (gdb) b *0x80482e8
            Breakpoint 1 at 0x80482e8: file sigint.c, line 6.
            (gdb) handle SIGINT noprint pass
            SIGINT is used by the debugger.
            Are you sure you want to change it? (y or n) y
            Signal        Stop    Print    Pass to program    Description
            SIGINT        No    No    Yes        Interrupt
            (gdb) r
            Starting program: /home/gj/signal/sigint

            2.2 向該程序發(fā)送信號: kill -INT 此程序的pid號
            hex@Gentoo ~/signal $ kill -INT 4639

            2.3 該程序收到信號后停在斷點處
            Breakpoint 1, sig_int (signo=2) at sigint.c:6
            6    {
            (gdb) i r esp
            esp            0xbfffe7ec    0xbfffe7ec
            (gdb) x/40a 0xbfffe7ec
            0xbfffe7ec:    0xb7fff400    0x2    0x33    0x0
            0xbfffe7fc:    0x7b    0x7b    0x8048930 <__libc_csu_init>    0x80488f0 <__libc_csu_fini>
            0xbfffe80c:    0xbfffed58    0xbfffed40    0x0    0x0
            0xbfffe81c:    0xbfffec18    0x0    0x0    0x0
            0xbfffe82c:    0x8048336 <main+58>    0x73    0x213    0xbfffed40
            0xbfffe83c:    0x7b    0xbfffead0    0x0    0x0
            0xbfffe84c:    0x0    0x0    0x0    0x0
            0xbfffe85c:    0x0    0x0    0x0    0x0
            0xbfffe86c:    0x0    0x0    0x0    0x0
            0xbfffe87c:    0x0    0x0    0x0    0x0
            棧上的內(nèi)容為信號棧sigframe:
            根據(jù)此結(jié)構(gòu)可以知道:
            1). 返回地址0xb7fff400,它指向vdso里的sigreturn
            (gdb) x/10i 0xb7fff400
               0xb7fff400 <__kernel_sigreturn>:    pop    %eax
               0xb7fff401 <__kernel_sigreturn+1>:    mov    $0x77,%eax
               0xb7fff406 <__kernel_sigreturn+6>:    int    $0x80
            這個地址根據(jù)內(nèi)核的不同而不同,我的內(nèi)核版本是2.6.38。
            2). 信號處理程序完成后,會回到 eip = 0x8048336 的地址繼續(xù)執(zhí)行。


            2.4 執(zhí)行完sig_int函數(shù)后,進入了__kernel_sigreturn,接著回到了代碼0x8048336處,一切恢復(fù)了正常。
            (gdb) x/5i $pc
            => 0x8048336 <main+58>:    jmp    0x8048336 <main+58>
            (gdb) i r esp
            esp            0xbfffed40    0xbfffed40

            在用戶層我們能看到的只有上面這么多信息了,可能有一個地方不能理解:在上面過程c中 從0xbfffe7ec起那一塊棧上的內(nèi)容從哪來的?(正常情況下堆棧esp應(yīng)該一直指向在過程d中顯示的esp值0xbfffed40)

            現(xiàn)在來看看在上面這些現(xiàn)象之下,內(nèi)核的堆棧發(fā)生了怎樣的變化。

            3. 內(nèi)核堆棧里發(fā)生的故事
            3.1 發(fā)信號時
            在 2.2 里當執(zhí)行kill -INT 4639后,pid為4639的程序(也就是我們運行的 ./sigint)會收到一個信號,但是信號實際都是在內(nèi)核里實現(xiàn)的。每個進程(這里只講進程的情況,線程類似,線程有一個tid)都有一個pid,與此pid對應(yīng)有一個結(jié)構(gòu) task_struct ,在task_struct里有一個變量 struct sigpending pending,當該進程收到信號時,并不會立即作出反應(yīng),只是讓內(nèi)核把這個信號記在了此變量里(它里面是一個鏈表結(jié)構(gòu))。當然,此時與內(nèi)核堆棧還沒有多大關(guān)系。

            3.2 檢測信號
              如果只記錄了信號,但沒有相應(yīng)反應(yīng),那有什么用啊。一個進程在什么 情況下會檢測信號的存在呢?在<情景分析>里說到了:“在中斷機制中,處理器的硬件在每條指令結(jié)束時都要檢測是否有中斷請求的存在。信號機制是純軟件的,當然不能依靠硬件來檢測信號的到來。同時,要在每條指令結(jié)束時都來檢測顯然是不現(xiàn)實的,甚至是不可能的。所以對信號的檢測機制是:每當從系統(tǒng)調(diào)用,中斷處理或異常處理返回到用戶空間的前夕;還有就是當進程被從睡眠中喚醒(必定是在系統(tǒng)調(diào)用中)的時候,此時若發(fā)現(xiàn)有信號在等待就要提前從系統(tǒng)調(diào)用返回。總而言之,不管是正常返回還是提前返回,在返回到用戶空間的前夕總是要檢測信號的存在并作出反應(yīng)。”

              因此,對收到的信號做出反應(yīng)的時間是 從內(nèi)核返回用戶空間的前夕,那么有那些情況會讓程序進入內(nèi)核呢?答案是中斷,異常和系統(tǒng)調(diào)用。簡單了解一下它們發(fā)生時內(nèi)核堆棧的變化。
              //-----中斷,異常,系統(tǒng)調(diào)用 : 開始
               1)在用戶空間發(fā)生中斷時,CPU會自動在內(nèi)核空間保存用戶堆棧的SS, 用戶堆棧的ESP, EFLAGS, 用戶空間的CS, EIP, 中斷號 - 256
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP | 中斷號 - 256
               進入內(nèi)核后,會進行一個SAVE_ALL,這樣內(nèi)核棧上的內(nèi)容為:
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP | 中斷號 - 256 | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX

               好了,一切都處理完時,內(nèi)核jmp到RESTORE_ALL(它是一個宏,例:在x86_32體系結(jié)構(gòu)下,/usr/src/kernel/arch/286/kernel/entry_32.S文件里包含該宏的定義)

               RESTORE做的工作,從它的代碼里就可以看出來了:   
               首先把棧上的 ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX pop到對應(yīng)的寄存器里
               然后將esp + 4 把 “中斷號 - 256” pop掉
               此時內(nèi)核棧上的內(nèi)容為:
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP
               最后執(zhí)行iret指令,此時CPU會從內(nèi)核棧上取出SS, ESP, ELFGAS, CS, EIP,然后接著運行。

               2) 在用戶空間發(fā)生異常時,CPU自動保存在內(nèi)核棧的內(nèi)容為:
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP | 出錯代碼 error_code
               (注:CPU只是在進入異常時才知道是否應(yīng)該把出錯代碼壓入堆棧(為什么?),而從異常處理通過iret指令返回時已經(jīng)時過境遷,CPU已經(jīng)無從知當初發(fā)生異常的原因,因此不會自動跳過這一項,而要靠相應(yīng)的異常處程序?qū)Χ褩<右哉{(diào)整,使得在CPU開始執(zhí)行iret指令時堆棧頂部是返回地址)

               進入內(nèi)核后,沒有進行SAVE_ALL,而是進入相應(yīng)的異常處理函數(shù)(這個函數(shù)是包裝后的,真正的處理函數(shù)在后面)(在此函數(shù)里會把真正的處理函數(shù)的地址push到棧上),然后jmp到各種異常處理所共用的程序入口error_code,它會像SAVE_ALL那樣保存相應(yīng)的寄存器(沒有保存ES),此時內(nèi)核空間上的內(nèi)容為:
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP | 出錯代碼 error_code | 相應(yīng)異常處理函數(shù)入口 | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
               (注:如果沒有出錯代碼,則此值為0)

               最后結(jié)束時與中斷類似(RESTORE_ALL)。

               3) 發(fā)生系統(tǒng)調(diào)用時,CPU自動保存在內(nèi)核棧的內(nèi)容為:
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP
               為了與中斷和異常的棧一致,在進入系統(tǒng)調(diào)用入口(ENTRY(system_call))后會首先push %eax,然后進行SAVE_ALL,此時內(nèi)核棧上的內(nèi)容為
               | 用戶堆棧的SS | 用戶堆棧的ESP | EFLAGS | 用戶空間的CS | EIP | EAX | ES | DS | EAX | EBP | EDI | ESI | EDX | ECX | EBX
             
               最后結(jié)束時與中斷類似(RESTORE_ALL)。
               //-----中斷,異常,系統(tǒng)調(diào)用 : 結(jié)束

               中斷,異常,系統(tǒng)調(diào)用這部分有一點遺漏的地方:檢測信號的時機就是緊挨著RESTORE_ALL之前發(fā)生的。

            3.3 對檢測到的信號做出反應(yīng)
              如果檢測到有要處理的信號時,就要開始做一些準備工作了,此時內(nèi)核里的內(nèi)容為(進入內(nèi)核現(xiàn)場時的內(nèi)容)
              | 用戶堆棧的SS1 | 用戶堆棧的ESP1 | EFLAGS1 | 用戶空間的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
              (注:?的值有三個選擇:中斷號 - 256/出錯代碼 error_code/出錯代碼 error_code)
              假設(shè)將要處理的信號對應(yīng)的信號處理程序是用戶自己設(shè)置的,即本文中SIGINT對應(yīng)的信號處理程序sig_int。
              現(xiàn)在要做的事情是讓cpu去執(zhí)行信號處理程序sig_int,但是執(zhí)行前需要做好準備工作:
              3.3.1  setup_frame
              在用戶空間設(shè)置好信號棧(struct sigframe)(假設(shè)設(shè)置好棧后esp的值為sigframe_esp,在本文中其值為0xbfffe7ec),即在2.3里看到的棧內(nèi)容。
              注:struct sigframe里至少包含以下內(nèi)容:
              用戶堆棧的SS1, 用戶堆棧的ESP1, EFLAGS1, 用戶空間的CS1, EIP1, ES1, DS1, EAX1, EBP1, EDI1, ESI1, EDX1, ECX1, EBX1

              3.3.2 設(shè)置即將運行的eip的值為信號處理函數(shù)sig_int的地址(為0x80482e8),并設(shè)置用戶ESP的值為sigframe_esp(為0xbfffe7ec),這是通過修改內(nèi)核棧里的EIP和ESP的值實現(xiàn)的,因為在從系統(tǒng)調(diào)用里iret時,會從內(nèi)核棧里取EIP,ESP。
              這時內(nèi)核棧的內(nèi)核為:
              | 用戶堆棧的SS1 | 0xbfffe7ec | EFLAGS1 | 用戶空間的CS1 | 0x80482e8 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
             
              最后,進行RESTORE_ALL,內(nèi)核棧上的內(nèi)容為:
              | 用戶堆棧的SS1 | 0xbfffe7ec | EFLAGS1 | 用戶空間的CS1 | 0x80482e8
             
              RESTORE_ALL里執(zhí)行完iret后,寄存器內(nèi)容為: EIP為0x80482e8(即sig_int),esp為0xbfffe7ec 。 于是用戶空間到了步驟 2.3

            3.4 信號處理程序完成以后
              2.3 -> 2.4,進入了sig_return系統(tǒng)調(diào)用,在sig_return里,內(nèi)核棧的內(nèi)容為(每個名字后面加一個2以便與前面的1區(qū)分)
              | 用戶堆棧的SS2 | 用戶堆棧的ESP2 | EFLAGS2 | 用戶空間的CS2 | EIP2 | ? | ES2 | DS2 | EAX2 | EBP2 | EDI2 | ESI2 | EDX2 | ECX2 | EBX2
              sig_return要做的主要工作就是根據(jù)用戶棧里sigframe的值修改內(nèi)核棧里的內(nèi)容,使內(nèi)核棧變?yōu)?
              | 用戶堆棧的SS1 | 用戶堆棧的ESP1 | EFLAGS1 | 用戶空間的CS1 | EIP1 | ? | ES1 | DS1 | EAX1 | EBP1 | EDI1 | ESI1 | EDX1 | ECX1 | EBX1
                                                              
              至此內(nèi)核棧里的內(nèi)容和進行信號處理前一樣了。經(jīng)過RESTORE_ALL后,用戶堆棧里的內(nèi)容也和以前一樣(主要指ESP的值一樣)。

              "kill -INT 4639" 只是一段小插曲。程序從原處開始運行。
            posted on 2011-07-26 18:27 hex108 閱讀(4070) 評論(0)  編輯 收藏 引用 所屬分類: Kernel
            久久精品国产精品青草app| 久久最近最新中文字幕大全 | 久久久久亚洲爆乳少妇无| 中文国产成人精品久久亚洲精品AⅤ无码精品| 国产精品久久久天天影视香蕉 | 久久久久亚洲AV无码麻豆| 久久播电影网| 99久久久国产精品免费无卡顿| 99久久亚洲综合精品成人| 久久久一本精品99久久精品88| 99久久免费只有精品国产| 久久久久亚洲av无码专区| 国产精品久久久久久久久久影院 | 久久天天躁狠狠躁夜夜不卡| 亚洲AV乱码久久精品蜜桃| 人妻无码精品久久亚瑟影视| 丁香狠狠色婷婷久久综合| 国内精品人妻无码久久久影院导航 | 99久久人妻无码精品系列蜜桃| 久久夜色精品国产亚洲| 99久久精品免费看国产| 久久九九亚洲精品| 久久精品国产亚洲AV麻豆网站| 欧美精品丝袜久久久中文字幕| 久久午夜电影网| 69SEX久久精品国产麻豆| 久久水蜜桃亚洲av无码精品麻豆| 亚洲成av人片不卡无码久久| 久久精品18| 久久精品无码专区免费| 91久久精品视频| 国内精品久久久久久久涩爱| 久久WWW免费人成—看片| 国产一区二区精品久久岳| 国产无套内射久久久国产| 久久国产一片免费观看| 久久久久18| yy6080久久| 97久久香蕉国产线看观看| 亚洲欧美精品伊人久久| 婷婷久久综合|