• <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>

            大龍的博客

            常用鏈接

            統計

            最新評論

            SEH的強大功能之一(轉)

            從本篇文章開始,將全面闡述__try,__except,__finally,__leave異常模型機制,它也即是Windows系列操作系統平臺上提供的SEH模型。主人公阿愚將在這里與大家分享SEH的學習過程和經驗總結。

              SEH有兩項非常強大的功能。當然,首先是異常處理模型了,因此,這篇文章首先深入闡述SEH提供的異常處理模型。另外,SEH還有一個特別強大的功能,這將在下一篇文章中進行詳細介紹。

            try-except入門

              SEH的異常處理模型主要由try-except語句來完成,它與標準C++所定義的異常處理模型非常類似,也都是可以定義出受監控的代碼模塊,以及定義異常處理模塊等。還是老辦法,看一個例子先,代碼如下:

            //seh-test.c
            #include <stdio.h>

            void main()
            {
            puts("hello");
            // 定義受監控的代碼模塊
            __try
            {
            puts("in try");
            }
            //定義異常處理模塊
            __except(1)
            {
            puts("in except");
            }
            puts("world");
            }

              呵呵!是不是很簡單,而且與C++異常處理模型很相似。當然,為了與C++異常處理模型相區別,VC編譯器對關鍵字做了少許變動。首先是在每個關鍵字加上兩個下劃線作為前綴,這樣既保持了語義上的一致性,另外也盡最大可能來避免了關鍵字的有可能造成名字沖突而引起的麻煩等;其次,C++異常處理模型是使用catch關鍵字來定義異常處理模塊,而SEH是采用__except關鍵字來定義。并且,catch關鍵字后面往往好像接受一個函數參數一樣,可以是各種類型的異常數據對象;但是__except關鍵字則不同,它后面跟的卻是一個表達式(可以是各種類型的表達式,后面會進一步分析)。

            try-except進階

              與C++異常處理模型很相似,在一個函數中,可以有多個try-except語句。它們可以是一個平面的線性結構,也可以是分層的嵌套結構。例程代碼如下:

            // 例程1
            // 平面的線性結構
            #include <stdio.h>

            void main()
            {
            puts("hello");
            __try
            {
            puts("in try");
            }
            __except(1)
            {
            puts("in except");
            }

            // 又一個try-except語句
            __try
            {
            puts("in try");
            }
            __except(1)
            {
            puts("in except");
            }

            puts("world");
            }


            // 例程2
            // 分層的嵌套結構
            #include <stdio.h>

            void main()
            {
            puts("hello");
            __try
            {
            puts("in try");
            // 又一個try-except語句
            __try
            {
            puts("in try");
            }
            __except(1)
            {
            puts("in except");
            }
            }
            __except(1)
            {
            puts("in except");
            }

            puts("world");
            }

             

            // 例程3
            // 分層的嵌套在__except模塊中
            #include <stdio.h>

            void main()
            {
            puts("hello");
            __try
            {
            puts("in try");
            }
            __except(1)
            {
            // 又一個try-except語句
            __try
            {
            puts("in try");
            }
            __except(1)
            {
            puts("in except");
            }

            puts("in except");
            }

            puts("world");
            }

            try-except異常處理規則

              try-except異常處理規則與C++異常處理模型有相似之處,例如,它們都是向上逐級搜索恰當的異常處理模塊,包括跨函數的多層嵌套try- except語句。但是,它們的處理規則也有另外一些很大的不同之處,例如查找匹配恰當的異常處理模塊的過程,在C++異常處理模型中,它是通過異常對象的類型來匹配;但是在try-except語句的異常處理規則中,則是通過__except關鍵字后面括號中的表達式的值來匹配查找正確的異常處理模塊。還是看看MSDN中怎么說的吧!摘略如下:

            The compound statement after the __try clause is the body or guarded section. The compound statement after the __except clause is the exception handler. The handler specifies a set of actions to be taken if an exception is raised during execution of the body of the guarded section. Execution proceeds as follows:
            1. The guarded section is executed.
            2. If no exception occurs during execution of the guarded section, execution continues at the statement after the __except clause.
            3. If an exception occurs during execution of the guarded section or in any routine the guarded section calls, the __except expression is evaluated and the value determines how the exception is handled. There are three values:
            EXCEPTION_CONTINUE_EXECUTION (–1) Exception is dismissed. Continue execution at the point where the exception occurred.
            EXCEPTION_CONTINUE_SEARCH (0) Exception is not recognized. Continue to search up the stack for a handler, first for containing try-except statements, then for handlers with the next highest precedence.
            EXCEPTION_EXECUTE_HANDLER (1) Exception is recognized. Transfer control to the exception handler by executing the __except compound statement, then continue execution at the assembly instruction that was executing when the exception was raised.
            Because the __except expression is evaluated as a C expression, it is limited to a single value, the conditional-expression operator, or the comma operator. If more extensive processing is required, the expression can call a routine that returns one of the three values listed above.

              對查找匹配恰當的異常處理模塊的過程等幾條規則翻譯如下:
              1. 受監控的代碼模塊被執行(也即__try定義的模塊代碼);
              2. 如果上面的代碼執行過程中,沒有出現異常的話,那么控制流將轉入到__except子句之后的代碼模塊中;
              3. 否則,如果出現異常的話,那么控制流將進入到__except后面的表達式中,也即首先計算這個表達式的值,之后再根據這個值,來決定做出相應的處理。這個值有三種情況,如下:
              EXCEPTION_CONTINUE_EXECUTION (–1) 異常被忽略,控制流將在異常出現的點之后,繼續恢復運行。
              EXCEPTION_CONTINUE_SEARCH (0) 異常不被識別,也即當前的這個__except模塊不是這個異常錯誤所對應的正確的異常處理模塊。系統將繼續到上一層的try-except域中繼續查找一個恰當的__except模塊。
              EXCEPTION_EXECUTE_HANDLER (1) 異常已經被識別,也即當前的這個異常錯誤,系統已經找到了并能夠確認,這個__except模塊就是正確的異常處理模塊。控制流將進入到__except模塊中。

              上面的規則其實挺簡單的,很好理解。當然,這個規則也非常的嚴謹,它能很好的滿足開發人員的各種需求,滿足程序員對異常處理的分類處理的要求,它能夠給程序員提供一個靈活的控制手段。

              其中比較特殊的就是__except關鍵字后面跟的表達式,它可以是各種類型的表達式,例如,它可以是一個函數調用,或是一個條件表達式,或是一個逗號表達式,或干脆就是一個整型常量等等。例如代碼如下:

            // seh-test.c
            // 異常處理模塊的查找過程演示
            #include <stdio.h>

            int seh_filer()
            {
            return 0;
            }

            void test()
            {
            __try
            {
            int* p;

            puts("test()函數的try塊中");

            // 下面將導致一個異常
            p = 0;
            *p = 45;
            }
            // 注意,__except關鍵字后面的表達式是一個函數表達式
            // 而且這個函數將返回0,所以控制流進入到上一層
            // 的try-except語句中繼續查找
            __except(seh_filer())
            {
            puts("test()函數的except塊中");
            }
            }

            void main()
            {
            puts("hello");
            __try
            {
            puts("main()函數的try塊中");

            // 注意,這個函數的調用過程中,有可能出現一些異常
            test();
            }
            // 注意,這個表達式是一個逗號表達式
            // 它前部分打印出一條message,后部分是
            // 一個常量,所以這個值也即為整個表達式
            // 的值,因此系統找到了__except定義的異
            // 常處理模塊,控制流進入到__except模塊里面
            __except(puts("in filter"), 1)
            {
            puts("main()函數的except塊中");
            }

            puts("world");
            }

              上面的程序運行結果如下:
              hello
              main()函數的try塊中
              test()函數的try塊中
              in filter
              main()函數的except塊中
              world
              Press any key to continue

              這種運行結果應該是在意料之中吧!為了對它的流程進行更清楚的分析,下圖描述出了程序的運行控制流轉移過程,如下。

            http://byfiles.storage.msn.com/x1pN1mp8dKYgTFQGzKRebME6105or51BGbjDrskKQ5x3cw-RNCRExH00cuzP8U6qAybuiCE9msw4yGhML-hVWfFOK8DnWPKA9WtlamUEAPnYIJs4c-bXtKEVQ

              另外,對于__except關鍵字后面表達式的值,上面的規則中已經做了詳細規定。它們有三種值,其中如果為0,那么系統繼續查找;如果為1,表示系統已經找到正確的異常處理模塊。其實這兩個值都很好理解,可是如果值為-1的話,那么處理將比較特殊,上面也提到了,此種情況下,“異常被忽略,控制流將在異常出現的點之后,繼續恢復運行。”實際上,這就等同于說,程序的執行過程將不受干擾,好像異常從來沒有發生一樣。看一個例程吧!代碼如下:

            #include <stdio.h>

            void main()
            {
            int j, zero;

            puts("hello");
            __try
            {
            puts("main()函數的try塊中");

            zero = 0;
            j = 10;
            // 下面將導致一個異常
            j = 45 / zero;

            // 注意,異常出現后,程序控制流又恢復到了這里
            printf("這里會執行到嗎?值有如何呢?j=%d \n", j);
            }
            // 注意,這里把zero變量賦值為1,試圖恢復錯誤,
            // 當控制流恢復到原來異常點時,避免了異常的再次發生
            __except(puts("in filter"), zero = 1, -1)
            {
            puts("main()函數的except塊中");
            }

            puts("world");
            }

            上面的程序運行結果如下:
            hello
            main()函數的try塊中
            in filter
            這里會執行到嗎?值有如何呢?j=45
            world
            Press any key to continue

              呵呵!厲害吧!要知道C++異常處理模型可沒有這樣的能力。但是請注意,一般這項功能不能輕易采用,為什么呢?因為它會導致不穩定,再看下面一個示例,代碼如下:

            #include <stdio.h>

            void main()
            {
            int* p, a;

            puts("hello");
            __try
            {
            puts("main()函數的try塊中");

            // 下面將導致一個異常
            p = 0;
            *p = 45;

            printf("這里會執行到嗎?值有如何呢?p=%d \n", *p);
            }
            // 注意,這里把p指針賦了一個合法的值,也即說,
            // 當控制流恢復到原來異常點時,異常將不會再次發生
            __except(puts("in filter"), p = &a, -1)
            {
            puts("main()函數的except塊中");
            }

            puts("world");
            }

              呵呵!大家猜猜上面的程序的運行結果如何呢?是不是和剛才的那個例子一樣,異常也得以被恢復了。朋友們!還是親自運行測試一把。哈哈!程序運行結果是死了,進行一個無限循環當中,并且控制終端內不斷輸出“in filter”信息。為什么會出現這種情況,難道MSDN中有關的闡述的有問題嗎?或這個異常處理模型實現上存在BUG?NO!不是這樣的,實際上這就是由于表達式返回-1值時,給程序所帶來的不穩定性。當然,MSDN中有關的闡述也沒有錯,那么究竟具體原因是為何呢?這是因為,表達式返回-1值時,系統將把控制流恢復到異常出現點之后繼續運行。這意味著什么呢?也許大家都明白了,它這里的異常恢復點是基于一條機器指令級別上的。這樣就有很大的風險,因為上面的例程中,所謂的異常恢復處理,也即p = &a語句,它實際上的確改變了p指針值,但是這個指針值是棧上的某個內存區域,而真正出現異常時,代表p指針值的很有可能是某個寄存器。呵呵!是不是挺費解的,沒關系!還是看看調試界圖吧!如下:

            http://byfiles.storage.msn.com/x1pN1mp8dKYgTFQGzKRebME62KovOI2zIOz3rCTVXO99-ku0JJm6Gg--r6FEmfo7NKpWBwPGnL6pvfxPZ2zpd1rP8MhDhEmzvQiib6XZ1TEJqu_HREcahE28g


            try-except深入

              上面的內容中已經對try-except進行了全面的了解,但是有一點還沒有闡述到。那就是如何在__except模塊中獲得異常錯誤的相關信息,這非常關鍵,它實際上是進行異常錯誤處理的前提,也是對異常進行分層分級別處理的前提。可想而知,如果沒有這些起碼的信息,異常處理如何進行?因此獲取異常信息非常的關鍵。Windows提供了兩個API函數,如下:

            LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
            DWORD GetExceptionCode(VOID);

              其中GetExceptionCode()返回錯誤代碼,而GetExceptionInformation()返回更全面的信息,看它函數的聲明,返回了一個LPEXCEPTION_POINTERS類型的指針變量。那么EXCEPTION_POINTERS結構如何呢?如下,

            typedef struct _EXCEPTION_POINTERS { // exp
            PEXCEPTION_RECORD ExceptionRecord;
            PCONTEXT ContextRecord;
            } EXCEPTION_POINTERS;

              呵呵!仔細瞅瞅,這是不是和上一篇文章中,用戶程序所注冊的異常處理的回調函數的兩個參數類型一樣。是的,的確沒錯!其中 EXCEPTION_RECORD類型,它記錄了一些與異常相關的信息;而CONTEXT數據結構體中記錄了異常發生時,線程當時的上下文環境,主要包括寄存器的值。因此有了這些信息,__except模塊便可以對異常錯誤進行很好的分類和恢復處理。不過特別需要注意的是,這兩個函數只能是在 __except后面的括號中的表達式作用域內有效,否則結果可能沒有保證(至于為什么,在后面深入分析異常模型的實現時候,再做詳細闡述)。看一個例程吧!代碼如下:

            #include <windows.h>
            #include <stdio.h>

            int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)
            {
            if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
            {
            printf("存儲保護異常\n");
            return 1;
            }
            else return 0;
            }

            int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)
            {
            if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
            {
            printf("被0除異常\n");
            return 1;
            }
            else return 0;
            }

            void main()
            {
            puts("hello");
            __try
            {
            __try
            {
            int* p;

            // 下面將導致一個異常
            p = 0;
            *p = 45;
            }
            // 注意,__except模塊捕獲一個存儲保護異常
            __except(exception_access_violation_filter(GetExceptionInformation()))
            {
            puts("內層的except塊中");
            }
            }
            // 注意,__except模塊捕獲一個被0除異常
            __except(exception_int_divide_by_zero_filter(GetExceptionInformation()))
            {
            puts("外層的except塊中");
            }

            puts("world");
            }

            上面的程序運行結果如下:
            hello
            存儲保護異常
            內層的except塊中
            world
            Press any key to continue

              呵呵!感覺不錯,大家可以在上面的程序基礎之上改動一下,讓它拋出一個被0除異常,看程序的運行結果是不是如預期那樣。

              最后還有一點需要闡述,在C++的異常處理模型中,有一個throw關鍵字,也即在受監控的代碼中拋出一個異常,那么在SEH異常處理模型中,是不是也應該有這樣一個類似的關鍵字或函數呢?是的,沒錯!SEH異常處理模型中,對異常劃分為兩大類,第一種就是上面一些例程中所見到的,這類異常是系統異常,也被稱為硬件異常;還有一類,就是程序中自己拋出異常,被稱為軟件異常。怎么拋出呢?還是Windows提供了的API函數,它的聲明如下:

            VOID RaiseException(
            DWORD dwExceptionCode, // exception code
            DWORD dwExceptionFlags, // continuable exception flag
            DWORD nNumberOfArguments, // number of arguments in array
            CONST DWORD *lpArguments // address of array of arguments
            );

              很簡單吧!實際上,在C++的異常處理模型中的throw關鍵字,最終也是對RaiseException()函數的調用,也即是說,throw是 RaiseException的上層封裝的更高級一類的函數,這以后再詳細分析它的代碼實現。這里還是看一個簡單例子吧!代碼如下:

            #include <windows.h>
            #include <stdio.h>

            int seh_filer(int code)
            {
            switch(code)
            {
            case EXCEPTION_ACCESS_VIOLATION :
            printf("存儲保護異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_DATATYPE_MISALIGNMENT :
            printf("數據類型未對齊異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_BREAKPOINT :
            printf("中斷異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_SINGLE_STEP :
            printf("單步中斷異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
            printf("數組越界異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_FLT_DENORMAL_OPERAND :
            case EXCEPTION_FLT_DIVIDE_BY_ZERO :
            case EXCEPTION_FLT_INEXACT_RESULT :
            case EXCEPTION_FLT_INVALID_OPERATION :
            case EXCEPTION_FLT_OVERFLOW :
            case EXCEPTION_FLT_STACK_CHECK :
            case EXCEPTION_FLT_UNDERFLOW :
            printf("浮點數計算異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_INT_DIVIDE_BY_ZERO :
            printf("被0除異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_INT_OVERFLOW :
            printf("數據溢出異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_IN_PAGE_ERROR :
            printf("頁錯誤異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_ILLEGAL_INSTRUCTION :
            printf("非法指令異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_STACK_OVERFLOW :
            printf("堆棧溢出異常,錯誤代碼:%x\n", code);
            break;
            case EXCEPTION_INVALID_HANDLE :
            printf("無效句病異常,錯誤代碼:%x\n", code);
            break;
            default :
            if(code & (1<<29))
            printf("用戶自定義的軟件異常,錯誤代碼:%x\n", code);
            else
            printf("其它異常,錯誤代碼:%x\n", code);
            break;
            }

            return 1;
            }

            void main()
            {
            puts("hello");
            __try
            {
            puts("try塊中");

            // 注意,主動拋出一個軟異常
            RaiseException(0xE0000001, 0, 0, 0);
            }
            __except(seh_filer(GetExceptionCode()))
            {
            puts("except塊中");
            }

            puts("world");
            }

            上面的程序運行結果如下:
            hello
            try塊中
            用戶自定義的軟件異常,錯誤代碼:e0000001
            except塊中
            world
            Press any key to continue

              上面的程序很簡單,這里不做進一步的分析。我們需要重點討論的是,在__except模塊中如何識別不同的異常,以便對異常進行很好的分類處理。毫無疑問,它當然是通過GetExceptionCode()或GetExceptionInformation ()函數來獲取當前的異常錯誤代碼,實際也即是DwExceptionCode字段。異常錯誤代碼在winError.h文件中定義,它遵循 Windows系統下統一的錯誤代碼的規則。每個DWORD被劃分幾個字段,如下表所示:

            http://byfiles.storage.msn.com/x1pN1mp8dKYgTFQGzKRebME6105or51BGbjDrskKQ5x3cw-RNCRExH00cuzP8U6qAybuiCE9msw4yGhML-hVWfFOK8DnWPKA9WtlamUEAPnYIJs4c-bXtKEVQ

              例如我們可以在winbase.h文件中找到EXCEPTION_ACCESS_VIOLATION的值為0 xC0000005,將這個異常代碼值拆開,來分析看看它的各個bit位字段的涵義。
            C 0 0 0 0 0 0 5 (十六進制)
            1100 0000 0000 0000 0000 0000 0000 0101 (二進制)
            第3 0位和第3 1位都是1,表示該異常是一個嚴重的錯誤,線程可能不能夠繼續往下運行,必須要及時處理恢復這個異常。第2 9位是0,表示系統中已經定義了異常代碼。第2 8位是0,留待后用。第1 6 位至2 7位是0,表示是FACILITY_NULL設備類型,它代表存取異常可發生在系統中任何地方,不是使用特定設備才發生的異常。第0位到第1 5位的值為5,表示異常錯誤的代碼。

              如果程序員在程序代碼中,計劃拋出一些自定義類型的異常,必須要規劃設計好自己的異常類型的劃分,按照上面的規則來填充異常代碼的各個字段值,如上面示例程序中拋出一個異常代碼為0xE0000001軟件異常。

            總結

              (1) C++異常模型用try-catch語法定義,而SEH異常模型則用try-except語法;

              (2) 與C++異常模型相似,try-except也支持多層的try-except嵌套。

              (3) 與C++異常模型不同的是,try-except模型中,一個try塊只能是有一個except塊;而C++異常模型中,一個try塊可以有多個catch塊。

              (4)與C++異常模型相似,try-except模型中,查找搜索異常模塊的規則也是逐級向上進行的。但是稍有區別的是,C++異常模型是按照異常對象的類型來進行匹配查找的;而try-except模型則不同,它通過一個表達式的值來進行判斷。如果表達式的值為1 (EXCEPTION_EXECUTE_HANDLER),表示找到了異常處理模塊;如果值為0 (EXCEPTION_CONTINUE_SEARCH),表示繼續向上一層的try-except域中繼續查找其它可能匹配的異常處理模塊;如果值為- 1(EXCEPTION_CONTINUE_EXECUTION),表示忽略這個異常,注意這個值一般很少用,因為它很容易導致程序難以預測的結果,例如,死循環,甚至導致程序的崩潰等。

               (5) __except關鍵字后面跟的表達式,它可以是各種類型的表達式,例如,它可以是一個函數調用,或是一個條件表達式,或是一個逗號表達式,或干脆就是一個整型常量等等。最常用的是一個函數表達式,并且通過利用GetExceptionCode()或GetExceptionInformation ()函數來獲取當前的異常錯誤信息,便于程序員有效控制異常錯誤的分類處理。

               (6) SEH異常處理模型中,異常被劃分為兩大類:系統異常和軟件異常。其中軟件異常通過RaiseException()函數拋出。RaiseException()函數的作用類似于C++異常模型中的throw語句。

              本篇文章已經對SEH的異常處理進行了比較全面而深入的闡述,相信大家現在已經對SEH的異常處理機制胸有成竹了。但是SEH的精華僅只如此嗎?非也,朋友們!繼續到下一篇的文章中,主人公阿愚將和大家一起共同探討SEH模型的另一項重要的機制,那就是“有效保證資源的清除”。這對于C程序可太重要了,因為在C++程序中,至少還有對象的析構函數來保證資源的有效清除,避免資源泄漏,但C語言中則沒有一個有效的機制,來完成此等艱巨的任務。呵呵!SEH 雪中送炭,它提供了完美的解決方案,所以千萬不要錯過,一起去看看吧!Let’s go!

            posted on 2008-01-25 19:30 大龍 閱讀(785) 評論(0)  編輯 收藏 引用

            久久99久久99小草精品免视看| 欧美日韩精品久久久免费观看| 国产精品99久久久久久人| 久久精品视频免费| 久久精品国产男包| 2021精品国产综合久久| 久久久久久久国产免费看| 久久精品成人欧美大片| 丁香五月综合久久激情| 亚洲精品白浆高清久久久久久| 久久精品国产WWW456C0M| 亚洲AV日韩精品久久久久久久| 狠狠色丁香婷婷综合久久来来去 | 久久综合一区二区无码| 日本精品久久久久久久久免费| 久久久综合香蕉尹人综合网| 无码人妻久久一区二区三区| 久久黄视频| 99久久国产综合精品成人影院| 亚洲人成伊人成综合网久久久| 国产精品久久久久乳精品爆| 国产美女久久久| 99热成人精品热久久669| 一本久久a久久精品vr综合| 久久精品亚洲男人的天堂| 999久久久无码国产精品| 无码伊人66久久大杳蕉网站谷歌 | 亚洲午夜无码AV毛片久久| 久久久精品一区二区三区| 99久久国语露脸精品国产| 久久久久人妻一区二区三区vr | 久久精品国产亚洲麻豆| 久久久免费精品re6| 久久精品亚洲一区二区三区浴池| 久久无码AV中文出轨人妻| 精品熟女少妇AV免费久久| 亚洲中文字幕无码久久精品1| 久久中文字幕精品| 亚洲欧洲日产国码无码久久99| 久久婷婷人人澡人人爽人人爱| 久久天天躁夜夜躁狠狠|