精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

搞懂 Core Dump,調試崩潰不用愁

系統 Linux
Core Dump 就是程序崩潰時,操作系統幫你 “凍結” 的一份 “現場快照”,里面記錄了程序崩潰瞬間的內存數據、函數調用棧、寄存器狀態等關鍵信息。只要掌握了它,你不用再靠 “猜” 和 “試”排查問題,就能精準定位到代碼里的 bug。

你是否也曾遇到過這樣的場景:寫好的程序在本地跑得好好的,一到線上就突然崩潰,日志里只留下一句模糊的 “Segmentation fault”;或者調試時遇到內存越界、空指針問題,反復排查代碼卻始終找不到癥結,只能對著屏幕發呆?這些讓人頭疼的崩潰問題,其實都有一個 “破局利器”——Core Dump。

簡單來說,Core Dump 就是程序崩潰時,操作系統幫你 “凍結” 的一份 “現場快照”,里面記錄了程序崩潰瞬間的內存數據、函數調用棧、寄存器狀態等關鍵信息。只要掌握了它,你不用再靠 “猜” 和 “試”排查問題,就能精準定位到代碼里的 bug。接下來內容不會講復雜的底層原理,而是聚焦實用技能:從怎么配置才能讓程序生成 Core Dump 文件,到用 GDB 快速分析快照找到崩潰原因,再到避開那些導致 Core Dump 生成失敗的坑。無論你是剛接觸 Linux 開發的新手,還是常跟線上疑難雜癥打交道的資深工程師,這套方法都能讓你在遇到程序突然“暴斃”時,做到心中有數、手中有術。

一、什么是 Core Dump?

1.1coredump文件介紹

在 Linux 系統下進行程序開發時,你是否遇到過程序突然崩潰,然后終端上出現 “Segmentation fault (core dumped)” 這樣的提示?這其實就是程序發生了 Core Dump。簡單來說,Core Dump(核心轉儲)是操作系統在進程收到某些信號而異常終止時,將進程地址空間的內容以及有關進程狀態的其他信息寫出的一個磁盤文件 。這個文件就像是程序崩潰瞬間的 “現場快照”,對我們調試程序、定位問題有著至關重要的作用。

想象一下,你精心編寫了一個復雜的程序,在測試過程中,它卻突然毫無征兆地崩潰了。如果沒有 Core Dump,你可能只能對著代碼干瞪眼,很難確定問題究竟出在哪里。但有了 Core Dump 文件,我們就可以借助調試工具(如 GDB),深入分析程序崩潰時的內存狀態、寄存器值、函數調用棧等信息,從而找出程序崩潰的真正原因 。比如說,是因為訪問了空指針,還是數組越界,亦或是其他隱藏在代碼深處的 Bug。

coredump文件格式如圖所示:

圖片圖片

coredump文件格式包含4個部分:ELF頭、程序頭表、NOTE段和LOAD段。

1.2從內核看coredump文件生成過程

coredump文件生成過程包含以下幾個步驟:

  • 步驟1:程序觸發致命信號,程序執行非法操作(如段錯誤、總線錯誤、除零或主動中止)時,會觸發CPU硬件異常并由操作系統內核向進程發送相應信號。
  • 步驟2:內核處理信號并終止進程,內核在向目標進程遞送信號后會立即凍結該進程以保持狀態穩定,隨后按照默認處理方式終止該進程并生成核心轉儲文件。
  • 步驟3:寫入Coredump文件,內核會在進程工作目錄創建核心轉儲文件(通常命名為core或core.<pid>),完整記錄崩潰時的進程虛擬地址空間、CPU寄存器狀態、線程信息、信號詳情及相關元數據。
  • 步驟4:資源清理與進程終止,在Coredump文件成功寫入后,內核會回收該進程占用的所有剩余資源(包括內存、文件描述符、信號量等),并最終將該進程完全從系統中移除。

我們可以通過下面這張圖來直觀理解coredump文件的生成過程:

圖片圖片

(1)信號處理階段:do_signal 函數

當進程從內核態返回用戶態之前,內核會仔細檢查進程的信號隊列,查看是否存在未處理的信號。這就好比一個快遞員在送完所有快遞后,會檢查自己的包裹清單,看看是否有遺漏的快遞。如果發現有未處理的信號,內核就會調用do_signal函數來處理這些信號。

do_signal函數在整個 coredump 文件生成過程中扮演著關鍵的角色,它主要調用get_signal_to_deliver函數來獲取需要處理的信號,并根據信號的類型和相關設置進行后續處理。下面是一段簡化的do_signal函數代碼示例(基于 Linux 內核源碼,實際代碼更為復雜,這里僅為展示關鍵邏輯):

static void fastcall do_signal(struct pt_regs *regs) {
    siginfo_t info;
    int signr;
    struct k_sigaction ka;
    sigset_t *oldset;

    // 調用get_signal_to_deliver函數獲取信號
    signr = get_signal_to_deliver(&info, &ka, regs, NULL); 
    if (signr > 0) {
        // 處理信號的邏輯
        // 這里可能會根據信號類型進行不同操作,比如生成coredump文件等
    }
}

在這段代碼中,get_signal_to_deliver函數的返回值signr表示獲取到的信號。如果signr大于 0,說明獲取到了有效的信號,接下來就會進入處理信號的邏輯。在實際的內核代碼中,這個處理過程涉及到復雜的信號處理機制,包括信號的屏蔽、恢復,以及根據信號類型執行相應的操作。

(2)獲取信號階段:get_signal_to_deliver 函數

get_signal_to_deliver函數的主要任務是從進程的信號隊列中獲取一個信號,并根據信號的類型進行不同的操作。它就像是從一個裝滿各種信號 “包裹” 的倉庫中,挑選出需要處理的 “包裹”。

這個函數會遍歷進程的信號隊列,檢查每個信號的狀態和屬性。對于一些特殊的信號,比如 SIGKILL(用于強制終止進程),會直接在內核態進行處理,不會生成 coredump 文件。而對于那些會導致進程異常退出并生成 coredump 文件的信號,如 SIGSEGV(段錯誤信號)、SIGABRT(異常終止信號)等,get_signal_to_deliver函數會進行相應的處理,為生成 coredump 文件做準備。

下面是一段get_signal_to_deliver函數的關鍵代碼分析(同樣是簡化后的代碼,用于展示核心邏輯):

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
                          struct pt_regs *regs, void *cookie) {
    sigset_t *mask = ¤t->blocked;
    int signr = 0;

    // 循環從信號隊列中獲取信號
    while ((signr = dequeue_signal(mask, ¤t->pending)) ||
           (signr = dequeue_signal(mask, ¤t->shared_pending))) {
        struct sigpending *pending;
        struct sigqueue *q;

        // 獲取信號對應的sigqueue
        q = find_signal_queue(signr, ¤t->pending);
        if (!q)
            q = find_signal_queue(signr, ¤t->shared_pending);

        // 填充信號信息
        *info = q->info;
        *return_ka = current->sigaction[signr - 1];

        // 處理與coredump相關的信號邏輯
        if (should_generate_coredump(signr)) {
            // 進行生成coredump文件的前期準備工作
            prepare_coredump();
        }

        // 其他信號處理邏輯

        return signr;
    }
    return 0;
}

在這段代碼中,通過dequeue_signal函數從進程的私有信號隊列current->pending和共享信號隊列current->shared_pending中獲取信號。如果獲取到信號,就會找到對應的sigqueue,填充信號信息到info和return_ka中。對于那些需要生成 coredump 文件的信號(通過should_generate_coredump函數判斷),會調用prepare_coredump函數進行前期準備工作,例如初始化一些與 coredump 生成相關的數據結構、檢查系統配置等 。

(3)內存信息記錄階段

當內核確定要生成 coredump 文件后,就會開始根據進程當時的內存信息來生成這個文件。這一步就像是給進程的內存狀態拍一張 “照片”,然后把這張 “照片” 保存到 coredump 文件中。

coredump 文件本質上是一個 ELF 格式的文件,它主要包含兩種類型的 segment:PT_NOTE 和 PT_LOAD。

PT_NOTE 類型的 segment 記錄了解析 memory 區域的關鍵信息。它被分成了多個elf_note結構,其中NT_PRSTATUS類型記錄了復位前 CPU 的寄存器信息,這些信息對于分析程序崩潰時的 CPU 狀態非常重要,就像是記錄了相機拍攝瞬間的各種參數設置;NT_TASKSTRUCT記錄了進程的task_struct信息,包含了進程的各種屬性和狀態;還有一個自定義的VMCOREINFO結構記錄了內核的一些關鍵信息,比如內核版本、編譯選項等,這些信息為分析提供了更多的上下文。

PT_LOAD 類型的 segment 則用于記錄進程的內存內容,每個 segment 對應一段內存區域,記錄了這段內存對應的物理地址、虛擬地址、長度以及訪問權限(讀、寫、執行等)。這些信息是通過遍歷進程中的每個虛擬內存區域(VMA,Virtual Memory Area)來設置的,然后將 VMA 的內容寫入到 coredump 文件中。例如,進程的堆、棧、數據段等都會在 PT_LOAD 類型的 segment 中有所體現,它們就像是照片中的各種物體,展示了程序運行時的內存布局情況。

通過這一系列的步驟,內核就完成了 coredump 文件的生成,為后續的程序調試和問題分析提供了重要的依據。

二、Core Dump 的生成機制

2.1觸發條件

Core Dump 的生成通常是由一些特定的信號觸發的。當程序接收到這些信號且沒有對其進行特殊處理時,就可能會產生 Core Dump。常見的能觸發 Core Dump 的信號有以下幾種:

①SIGSEGV(信號 11):這個信號表示段錯誤(Segmentation Fault),通常是由于程序進行了非法的內存訪問操作。比如訪問空指針、數組越界或者使用已經釋放的內存。以 C 語言代碼為例:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = NULL;
    *ptr = 10; // 訪問空指針,會觸發SIGSEGV信號
    return 0;
}

在這段代碼中,我們定義了一個空指針ptr,然后嘗試向它所指向的位置寫入數據,這是非法的內存訪問操作,會導致程序接收到 SIGSEGV 信號,進而可能產生 Core Dump。

②SIGABRT(信號 6):此信號一般由程序調用abort函數引發,或者在斷言(assert)失敗時產生。例如:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main() {
    int num = 0;
    assert(num > 0); // 斷言失敗,會觸發SIGABRT信號
    return 0;
}

這里我們使用assert來檢查num是否大于 0,由于num的值為 0,斷言失敗,程序會觸發 SIGABRT 信號,可能生成 Core Dump 文件。

③SIGFPE(信號 8):該信號在發生致命的算術運算錯誤時發出,比如除零操作。看下面的代碼:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 0;
    int c = a / b; // 除零操作,會觸發SIGFPE信號
    return 0;
}

這段代碼中進行了除零運算,這是一種致命的算術錯誤,會導致程序接收到 SIGFPE 信號,有很大概率產生 Core Dump。

2.2配置要點

在 Linux 系統中,要讓程序在崩潰時生成 Core Dump 文件,需要進行一些配置。

  1. ulimit -c 命令:默認情況下,系統對 Core 文件的大小限制可能為 0,這意味著程序崩潰時不會生成 Core 文件。我們可以使用ulimit -c命令來設置 Core 文件大小的上限。如果想要生成不受大小限制的 Core 文件,可以執行ulimit -c unlimited命令。比如,在終端中先執行ulimit -c unlimited,然后再運行可能會崩潰的程序,這樣當程序異常崩潰時,就更有可能生成完整的 Core Dump 文件。
  2. 程序運行目錄權限:程序運行的當前目錄必須對進程具有寫權限,否則無法將 Core 文件保存到該目錄。假設我們的程序在/home/user/app目錄下運行,如果該目錄沒有寫入權限,即使程序崩潰觸發了 Core Dump 生成條件,也無法在該目錄下生成 Core 文件。
  3. seteuid ()/setegid () 函數調用后的特殊處理:如果程序在運行過程中調用了seteuid()或setegid()函數來改變進程的有效用戶 ID 或組 ID ,默認情況下系統不會為這類進程生成 Core 文件。此時,需要將/proc/sys/fs/suid_dumpable文件的內容修改為 1,才能夠讓這類進程在崩潰時生成 Core 文件。例如,通過echo 1 > /proc/sys/fs/suid_dumpable命令來修改該文件內容,以允許生成 Core 文件。

還可以通過修改/proc/sys/kernel/core_pattern文件來指定Core文件的生成路徑和命名規則,方便我們更好地管理生成的Core Dump文件。比如,執行echo "/var/core/%e.%p.%h.%t" > /proc/sys/kernel/core_pattern,這樣生成的Core文件會保存在/var/core目錄下,文件名包含程序名(%e)、進程 ID(%p)、主機名(%h)和時間戳(%t),有助于我們區分不同程序、不同進程產生的Core Dump文件 。

三、引發 Core Dump 的常見原因

3.1內存訪問錯誤

(1)空指針或野指針解引用:在 C/C++ 等語言中,空指針是指未指向任何有效內存地址的指針,野指針則是指向一塊已經釋放或者從未被初始化的內存區域的指針。當對空指針或野指針進行解引用操作時,就相當于在訪問一塊無效的內存,這會觸發 SIGSEGV 信號,導致程序崩潰并生成 Core Dump。比如在下面這段 C++ 代碼中:

#include <iostream>

int main() {
    int *ptr = nullptr;
    *ptr = 10; // 解引用空指針,會觸發SIGSEGV信號
    return 0;
}

運行這段代碼,程序會因為訪問空指針而接收到 SIGSEGV 信號,進而產生 Core Dump。

(2)緩沖區溢出:當我們向一個數組或緩沖區寫入超出其大小的數據時,就會發生緩沖區溢出。這不僅會覆蓋相鄰的內存區域,破壞其他數據的完整性,還可能導致程序執行到非法的內存地址,引發 SIGSEGV 信號,最終造成 Core Dump。以下面的 C 語言代碼為例:

#include <stdio.h>

int main() {
    char buffer[10];
    // 試圖向buffer中寫入長度為15的字符串,會導致緩沖區溢出
    strcpy(buffer, "123456789012345"); 
    return 0;
}

這里使用strcpy函數向長度為 10 的buffer數組中寫入長度為 15 的字符串,會造成緩沖區溢出,程序很可能會崩潰并生成 Core Dump。

3.2信號未正確處理

在程序運行過程中,會收到各種各樣的信號。其中一些關鍵信號,如果沒有被程序捕獲并進行特殊處理,它們的默認行為就是終止進程并生成 Core Dump 。

(1)SIGSEGV:前面已經多次提到,這個信號代表段錯誤,主要是由于非法內存訪問引發的。像訪問空指針、數組越界、使用已釋放的內存等操作,都會讓程序收到 SIGSEGV 信號,若不捕獲處理,就會導致 Core Dump。

(2)SIGABRT:通常由程序調用abort函數,或者斷言(assert)失敗時觸發。比如在下面的代碼中:

#include <stdio.h>
#include <assert.h>

int main() {
    int num = 0;
    assert(num > 0); // 斷言失敗,觸發SIGABRT信號
    return 0;
}

由于num的值為 0,斷言assert(num > 0)失敗,程序會收到 SIGABRT 信號,若未捕獲此信號,就會產生 Core Dump。

(3)SIGFPE:該信號表示發生了致命的算術運算錯誤,例如除零操作。如下代碼:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 0;
    int c = a / b; // 除零操作,觸發SIGFPE信號
    return 0;
}

這段代碼進行了除零運算,會觸發 SIGFPE 信號,在未捕獲處理的情況下,程序會終止并生成 Core Dump。

3.3資源限制與配置問題

在 Linux 系統中,ulimit -c用于設置 Core 文件大小的上限。如果這個值被設置為 0(默認情況下可能如此),那么即使程序崩潰觸發了 Core Dump 的生成條件,也不會產生 Core 文件。只有將ulimit -c設置為一個大于 0 的值,或者設置為unlimited(不限制大小),程序崩潰時才有可能生成 Core Dump 文件。比如,在終端中執行ulimit -c 1024,表示將 Core 文件大小上限設置為 1024KB ,若程序崩潰產生的 Core 文件大小超過這個限制,可能無法完整生成。

當程序崩潰需要生成 Core Dump 文件時,如果磁盤空間不足,文件無法成功寫入,也就無法生成有效的 Core Dump 文件。假設磁盤剩余空間只有 10MB,而程序崩潰時產生的 Core 文件預計大小為 20MB,那么就無法生成該 Core 文件,這會給我們后續調試程序帶來困難。

程序必須對生成 Core Dump 文件的目標路徑具有寫權限。例如,若程序嘗試在/var/core目錄下生成 Core 文件,但該程序運行的用戶對/var/core目錄沒有寫權限,就無法成功生成 Core Dump 文件。只有確保程序對目標路徑有正確的讀寫權限,才能順利生成 Core 文件,以便后續分析調試。

3.4多線程問題

在多線程環境下,程序的執行流程變得更加復雜,一些潛在的問題可能導致 Core Dump。

(1)競態條件:當多個線程同時訪問和修改共享資源,并且沒有進行適當的同步控制時,就會出現競態條件。這可能導致數據的不一致性,甚至程序崩潰并生成 Core Dump。比如,多個線程同時對一個全局變量進行讀寫操作,沒有加鎖保護:

#include <iostream>
#include <thread>

int sharedVariable = 0;

void increment() {
    for (int i = 0; i < 1000; ++i) {
        sharedVariable++; // 多個線程同時訪問和修改,沒有同步
    }
}

int main() {
    std::thread thread1(increment);
    std::thread thread2(increment);

    thread1.join();
    thread2.join();

    std::cout << "Final value of sharedVariable: " << sharedVariable << std::endl;
    return 0;
}

在這段代碼中,sharedVariable是共享資源,increment函數被兩個線程同時調用,由于沒有加鎖等同步機制,可能會出現競態條件,導致程序運行結果不確定,甚至可能引發 Core Dump。

(2)死鎖:當兩個或多個線程相互等待對方釋放資源,形成一種僵持的狀態,就發生了死鎖。死鎖會使程序無法繼續執行,最終可能導致 Core Dump。以下是一個簡單的死鎖示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1;
std::mutex mutex2;

void threadFunction1() {
    mutex1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    mutex2.lock(); // 等待mutex2,而mutex2被threadFunction2持有
    mutex2.unlock();
    mutex1.unlock();
}

void threadFunction2() {
    mutex2.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    mutex1.lock(); // 等待mutex1,而mutex1被threadFunction1持有
    mutex1.unlock();
    mutex2.unlock();
}

int main() {
    std::thread thread1(threadFunction1);
    std::thread thread2(threadFunction2);

    thread1.join();
    thread2.join();

    return 0;
}

在這個例子中,threadFunction1和threadFunction2相互等待對方持有的鎖,形成死鎖,程序會陷入停滯,最終可能崩潰產生 Core Dump。

3.5動態內存管理錯誤

(1)雙重釋放:在 C/C++ 中,當對一塊已經釋放的內存再次調用釋放函數(如free或delete)時,就會發生雙重釋放。這是一種未定義行為,可能導致程序崩潰并生成 Core Dump。例如:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int *)malloc(sizeof(int));
    free(ptr);
    free(ptr); // 雙重釋放,會導致未定義行為,可能引發Core Dump
    return 0;
}

這段代碼中,ptr指向的內存被釋放了兩次,這是非常危險的操作,極有可能導致程序出現異常,進而產生 Core Dump。

(2)內存泄漏:雖然內存泄漏本身不會直接導致 Core Dump,但隨著程序的長時間運行,不斷泄漏的內存會逐漸耗盡系統資源。當系統沒有足夠的內存供程序使用時,程序就可能會崩潰并生成 Core Dump。比如在下面的 C++ 代碼中:

#include <iostream>

void memoryLeakFunction() {
    while (true) {
        int *ptr = new int; // 不斷分配內存,但沒有釋放
    }
}

int main() {
    memoryLeakFunction();
    return 0;
}

memoryLeakFunction函數中不斷使用new分配內存,卻沒有使用delete釋放,隨著循環的進行,內存會不斷被消耗,最終可能導致程序因內存不足而崩潰,生成 Core Dump。

3.6程序邏輯錯誤

(1)無限遞歸:當一個函數不斷調用自身,沒有終止條件時,就會發生無限遞歸。每一次遞歸調用都會在棧上分配新的空間,隨著遞歸深度的增加,棧空間會被耗盡,導致棧溢出,進而引發 Core Dump。例如:

#include <stdio.h>

void recursiveFunction() {
    recursiveFunction(); // 無限遞歸,會導致棧溢出
}

int main() {
    recursiveFunction();
    return 0;
}

在這段代碼中,recursiveFunction函數沒有任何終止條件,會一直遞歸調用自身,最終導致棧溢出,程序崩潰并生成 Core Dump。

(2)未捕獲異常:在 C++ 等支持異常處理的語言中,如果程序拋出了異常,但沒有在合適的地方捕獲并處理它,異常會向上層傳遞。如果最終沒有被捕獲,程序就會異常終止,可能生成 Core Dump。例如:

#include <iostream>

void functionThatThrows() {
    throw 1; // 拋出異常
}

int main() {
    try {
        functionThatThrows();
    } catch (...) {
        // 這里沒有捕獲到異常,異常會繼續向上傳遞
    }
    return 0;
}

在這段代碼中,functionThatThrows函數拋出了一個異常,但在main函數中沒有被正確捕獲,異常會導致程序異常終止,有很大概率生成 Core Dump。

3.7硬件問題

雖然相對較少見,但硬件問題也可能引發 Core Dump。

  • 內存故障:當計算機的物理內存出現故障時,程序在訪問內存時可能會出現錯誤。比如內存中的某些存儲單元損壞,導致讀取或寫入數據時出現異常,這可能觸發 SIGSEGV 等信號,進而使程序崩潰并生成 Core Dump。
  • CPU 異常:CPU 出現硬件故障或者過熱等異常情況時,也可能影響程序的正常執行。例如,CPU 在執行指令時發生錯誤,無法正確處理程序的請求,這可能導致程序崩潰,生成 Core Dump 文件 。不過,這類由硬件問題導致的 Core Dump,排查起來相對困難,需要結合硬件檢測工具來確定具體原因。

四、Core Dump 的分析與調試

4.1使用 GDB 分析 Core Dump 文件

當程序崩潰生成 Core Dump 文件后,我們就可以使用 GDB(GNU 調試器)來分析它,從而找出程序崩潰的原因。GDB 是一個功能強大的調試工具,在 Linux 系統下被廣泛應用于程序調試。

假設我們有一個名為my_program的可執行文件,以及它崩潰時生成的core.1234的 Core Dump 文件。首先,我們需要打開終端,進入到 Core Dump 文件和可執行文件所在的目錄。然后,在終端中輸入以下命令,使用 GDB 加載 Core Dump 文件和可執行文件:

gdb ./my_program core.1234

執行上述命令后,GDB 會加載相關信息,并顯示一些關于程序崩潰的初步信息,比如程序是因為接收到什么信號而崩潰的。

接下來,我們使用bt(backtrace 的縮寫)命令來查看程序崩潰時的調用棧。調用棧就像是程序執行路徑的 “歷史記錄”,它展示了從程序入口開始,到崩潰點之前,函數的調用順序。通過分析調用棧,我們可以了解程序的執行流程,進而定位到崩潰發生的具體函數和代碼行。在 GDB 的命令行中輸入bt,然后回車,GDB 會輸出類似如下的內容:

#0  0x00007ffff7a0c397 in strlen () from /lib64/libc.so.6
#1  0x000000000040055d in main () at test.c:5

從這個輸出中,我們可以看到,程序在test.c文件的第 5 行發生了崩潰,當時正在調用strlen函數。這就為我們定位問題提供了重要線索,我們可以進一步查看test.c文件的第 5 行及其相關代碼,來分析為什么會在這里崩潰。

除了bt命令,GDB 還有許多其他有用的命令,比如info locals可以查看當前函數的局部變量,print命令可以打印變量的值,list命令可以列出源代碼等 。這些命令結合使用,能夠幫助我們更全面、深入地分析 Core Dump 文件,找出程序崩潰的根本原因。例如,我們可以使用print命令來查看某個變量在崩潰時的值,假設在上述崩潰案例中,我們想查看test.c文件中第 5 行涉及的某個變量str的值,可以在 GDB 中輸入print str,GDB 會輸出該變量的值,這有助于我們判斷變量是否符合預期,從而進一步分析問題。

4.2檢查系統配置

在進行 Core Dump 分析之前,確保系統配置正確是非常重要的,這直接影響到我們能否成功生成和分析 Core Dump 文件。

(1)ulimit -c 設置:正如前面提到的,ulimit -c用于設置 Core 文件大小的上限。如果這個值被設置為 0,程序崩潰時將不會生成 Core 文件。因此,我們需要確保ulimit -c的值不是 0,最好設置為unlimited,以允許生成完整的 Core 文件。可以通過以下命令來檢查當前的ulimit -c設置:

ulimit -c

如果輸出為 0,我們可以使用以下命令將其設置為不限制大小:

ulimit -c unlimited

需要注意的是,ulimit命令的設置通常只對當前終端會話有效。如果希望永久生效,可以將相關設置添加到用戶的配置文件(如~/.bashrc或~/.zshrc)中。

(2)Core Dump 存儲路徑權限:程序必須對生成 Core Dump 文件的目標路徑具有寫權限。如果路徑權限不足,即使程序崩潰觸發了 Core Dump 生成條件,也無法生成有效的 Core 文件。比如,我們在/var/core目錄下生成 Core 文件,就需要確保運行程序的用戶對/var/core目錄有寫權限。可以使用以下命令來檢查目錄權限:

ls -l /var/core

如果發現權限不足,可以使用chmod命令來修改權限,例如:

sudo chmod 777 /var/core

這樣,所有用戶都對/var/core目錄具有讀、寫和執行權限,確保程序能夠在該目錄下成功生成 Core Dump 文件 。另外,還需要保證生成 Core Dump 文件的路徑所在磁盤有足夠的空間,避免因磁盤空間不足導致 Core 文件無法生成。

4.3GDB 加載 Core Dump 文件

在 Linux 系統中,GDB(GNU Debugger)是一款強大的調試工具,在分析 Core Dump 文件時發揮著關鍵作用。使用 GDB 加載可執行文件和 Core Dump 文件的操作相對簡單。假設我們有一個名為my_program的可執行文件,以及對應的 Core Dump 文件core.1234(這里的1234為進程 ID,實際使用時需根據具體情況替換),在終端中輸入以下命令即可啟動 GDB 并加載相關文件:

gdb my_program core.1234

執行該命令后,GDB 會自動加載可執行文件和 Core Dump 文件,并停留在程序崩潰時的位置。此時,我們就可以利用 GDB 提供的各種命令對 Core Dump 進行深入分析,探尋程序崩潰的原因。

①where/bt—— 查看堆棧信息

在 GDB 中,where和bt(backtrace 的縮寫)命令功能相近,主要用于查看當前線程的函數調用堆棧信息。這就像是沿著程序崩潰時的 “足跡”,一步步回溯到程序的入口點,幫助我們清晰地了解程序執行的路徑,從而找到問題所在。

當程序崩潰時,使用bt命令,GDB 會輸出函數調用的序列,每一行都包含了函數名、所在文件以及行號等重要信息。例如:

(gdb) bt
#0  func3 (arg1=0x7fffffffde10, arg2=42) at my_file.c:123
#1  0x00005555555552b5 in func2 (arg=0x7fffffffde10) at main.c:234
#2  0x0000555555555350 in main () at main.c:345

從上述輸出中可以看出,程序崩潰時正在執行func3函數,該函數位于my_file.c文件的第 123 行,而func3是由func2調用的,func2又在main函數中被調用。通過這樣的堆棧信息,我們能夠快速定位到程序崩潰的大致位置,進而深入分析問題。

②p—— 查看變量值

p(print 的縮寫)命令用于打印變量的值,這在分析 Core Dump 時非常實用。通過查看變量在程序崩潰時的值,可以判斷程序的運行狀態是否符合預期,從而發現潛在的問題。

例如,我們懷疑某個變量在程序崩潰時的值異常,可使用p命令查看其值。假設我們要查看變量my_variable的值,在 GDB 中輸入:

(gdb) p my_variable

GDB 會輸出my_variable的值。如果該變量是一個復雜的數據結構,如結構體或數組,p命令也能以相應的格式展示其內容。例如,對于一個結構體變量my_struct,輸入p my_struct,GDB 會顯示結構體中各個成員的值。這有助于我們全面了解程序崩潰時變量的狀態,為問題排查提供有力支持。

③ info registers—— 查看寄存器信息

info registers命令用于顯示當前寄存器的內容。寄存器是 CPU 中用于臨時存儲數據的高速存儲單元,程序運行過程中的各種數據處理和指令執行都與寄存器密切相關。通過查看寄存器在程序崩潰時的狀態,我們可以獲取更多關于程序運行的底層信息,這對于深入分析程序崩潰原因至關重要。

在 GDB 中輸入info registers,會輸出一系列寄存器及其對應的值。例如:

(gdb) info registers
rax            0x0      0
rbx            0x7ffff7fc1a40   140737351884352
rcx            0x1      1
rdx            0x7ffff7bc8723   140737351871779
...

這些寄存器的值反映了程序崩潰瞬間 CPU 的工作狀態,結合其他調試信息,能夠幫助我們更全面地理解程序崩潰的原因,尤其是在涉及到硬件相關的問題時,寄存器信息的分析尤為重要。

五、Core Dump實戰案例

5.1簡單案例分析

下面通過一個簡單的代碼示例,來看看如何利用 GDB 對 Core Dump 進行分析。假設有如下一段 C 語言代碼:

#include <stdio.h>
#include <stdlib.h>

void func() {
    int *ptr = NULL;
    *ptr = 10; // 這里會導致空指針解引用,引發Core Dump
}

int main() {
    func();
    return 0;
}

在上述代碼中,func函數內定義了一個空指針ptr,并嘗試對其進行解引用操作,這必然會引發程序崩潰。為了讓 GDB 能夠更好地分析問題,在編譯時需要加上-g選項,以生成調試信息。編譯命令如下:

gcc -g -o test test.c

運行該程序后,程序會因為空指針解引用而崩潰,并生成 Core Dump 文件(前提是已正確配置 Core Dump 生成,如設置ulimit -c unlimited)。假設生成的 Core Dump 文件名為core.12345(12345為進程 ID)。

接下來,使用 GDB 加載可執行文件和 Core Dump 文件進行分析:

gdb test core.12345

進入 GDB 環境后,使用bt命令查看堆棧信息:

(gdb) bt
#0  func () at test.c:5
#1  0x0000555555555199 in main () at test.c:9

從輸出結果可以清晰地看到,程序在test.c文件的第 5 行發生崩潰,此時正在執行func函數,而func函數是由main函數調用的。再使用p命令查看ptr變量的值:

(gdb) p ptr
$1 = (int *) 0x0

由此可知,ptr確實是一個空指針,這就是導致程序崩潰并產生 Core Dump 的原因。通過這個簡單的案例,我們初步領略了 GDB 在分析 Core Dump 文件時的強大功能,它能夠快速準確地定位到問題所在,為開發者節省大量的調試時間。

5.2復雜場景實戰

在實際的項目開發中,多線程程序的 Core Dump 問題往往更加復雜和難以排查。下面分享一個多線程程序出現 Core Dump 的案例,以及如何運用 GDB 及多線程調試命令來解決問題。

假設有一個多線程程序,其功能是多個線程同時對一個共享數組進行讀寫操作。部分代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define ARRAY_SIZE 100
int shared_array[ARRAY_SIZE];
pthread_mutex_t mutex;

void *write_thread(void *arg) {
    for (int i = 0; i < ARRAY_SIZE; i++) {
        pthread_mutex_lock(&mutex);
        shared_array[i] = i;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void *read_thread(void *arg) {
    for (int i = 0; i < ARRAY_SIZE; i++) {
        pthread_mutex_lock(&mutex);
        int value = shared_array[i];
        printf("Read value: %d at index %d\n", value, i);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t write_tid, read_tid;
    pthread_mutex_init(&mutex, NULL);

    if (pthread_create(&write_tid, NULL, write_thread, NULL)!= 0) {
        perror("Failed to create write thread");
        return 1;
    }
    if (pthread_create(&read_tid, NULL, read_thread, NULL)!= 0) {
        perror("Failed to create read thread");
        return 1;
    }

    if (pthread_join(write_tid, NULL)!= 0) {
        perror("Failed to join write thread");
        return 1;
    }
    if (pthread_join(read_tid, NULL)!= 0) {
        perror("Failed to join read thread");
        return 1;
    }

    pthread_mutex_destroy(&mutex);
    return 0;
}

在這個程序中,我們創建了一個寫線程和一個讀線程,它們通過互斥鎖mutex來保證對共享數組shared_array的安全訪問。然而,在實際運行過程中,程序偶爾會出現 Core Dump 現象。

為了調試這個問題,首先確保在編譯時加上-g選項,以生成調試信息:

gcc -g -o multi_thread_test multi_thread_test.c -lpthread

運行程序后,當 Core Dump 發生時,假設生成的 Core Dump 文件名為core.67890。使用 GDB 加載可執行文件和 Core Dump 文件:

gdb multi_thread_test core.67890

進入 GDB 環境后,首先使用info threads命令查看所有線程的信息:

(gdb) info threads
  Id   Target Id         Frame
  1    Thread 0x7ffff7fda700 (LWP 67890) "multi_thread_test" main () at multi_thread_test.c:32
  2    Thread 0x7ffff77ef700 (LWP 67891) "multi_thread_test" read_thread (arg=0x0) at multi_thread_test.c:18
  3    Thread 0x7ffff6fee700 (LWP 67892) "multi_thread_test" write_thread (arg=0x0) at multi_thread_test.c:10

從輸出結果可以看到,程序中有三個線程,其中線程 1 是主線程,線程 2 是讀線程,線程 3 是寫線程。接下來,我們需要切換到發生問題的線程進行分析。假設通過觀察,發現線程 2 在讀取共享數組時出現了 Core Dump。使用thread 2命令切換到線程 2:

(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff77ef700 (LWP 67891))]
#0  read_thread (arg=0x0) at multi_thread_test.c:20

此時,我們已經切換到讀線程,并且 GDB 停在了讀線程發生問題的代碼行。使用bt命令查看讀線程的堆棧信息:

(gdb) bt
#0  read_thread (arg=0x0) at multi_thread_test.c:20
#1  0x00007ffff7bc8723 in pthread_mutex_lock () from /lib/x86_64-linux-gnu/libpthread.so.0
#2  0x00005555555552b5 in main () at multi_thread_test.c:28

從堆棧信息可以看出,讀線程在執行pthread_mutex_lock函數時出現了問題。進一步使用p命令查看相關變量的值,例如查看i的值:

(gdb) p i
$1 = 120

發現i的值超出了共享數組的邊界ARRAY_SIZE(這里ARRAY_SIZE為 100),這就是導致 Core Dump 的原因。原來是在多線程環境下,由于線程調度的不確定性,讀線程在寫線程尚未完全初始化共享數組時,就嘗試讀取了越界的位置,從而引發了錯誤。通過這個復雜場景的實戰案例,我們可以看到,在多線程程序中,利用 GDB 的多線程調試命令,能夠逐步排查出 Core Dump 的根源,為解決復雜的多線程問題提供了有力的手段。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2010-06-02 09:31:43

Linux core

2021-04-20 09:52:43

Linuxcore dump代碼

2020-09-11 16:17:02

產品定價AI人工智能

2025-05-20 08:40:00

2010-06-09 08:39:34

2010-04-07 16:50:41

雙線解析DNS巧搭建

2021-05-01 20:36:01

隨身WiFiWiFi網絡

2011-12-29 11:57:08

WA2612無線信號

2019-12-26 09:52:33

Redis集群線程

2017-08-16 15:11:10

ELK集群監控

2020-10-27 10:43:24

Redis字符串數據庫

2015-07-06 11:57:18

移動設備安全移動安全策略

2021-10-20 18:46:45

人工智能AI無人機

2009-08-27 16:53:05

C# using作用

2011-12-08 10:25:19

戴爾IT設備升級解決方案

2009-04-29 14:46:15

ADSL寬帶掉線

2010-08-13 09:50:53

桌面虛擬化

2025-03-31 02:20:00

2010-06-02 09:01:20

Linux core
點贊
收藏

51CTO技術棧公眾號

国产精品久久777777毛茸茸| 丝袜久久网站| 亚洲精品菠萝久久久久久久| 国产精品一区二区在线观看| www欧美在线| 久久免费精品视频在这里| 欧美一区二区三区不卡| 免费一级特黄毛片| 成人免费在线视频网| 久久99精品久久久久久动态图 | 精品日本美女福利在线观看| 日韩电影免费观看在| 国产偷拍一区二区| 国产手机视频一区二区| www国产亚洲精品久久网站| 中文字幕av一区二区三区人妻少妇| 97超碰免费在线| 中文一区二区在线观看| 国产精品一区二区三区在线| 一级特黄录像免费看| 99国产成+人+综合+亚洲欧美| 国产亚洲欧美日韩一区二区| 国产亚洲精品成人a| 成人黄色免费观看| 午夜精品国产更新| 成人在线观看毛片| 超碰免费在线| 不卡的av电影| 亚洲一区二区中文| 日韩乱码一区二区三区| 99av国产精品欲麻豆| 日韩在线中文字幕| 国产三级av在线播放| 91欧美极品| 91精品国产综合久久久蜜臀图片| 18禁免费无码无遮挡不卡网站| www在线观看播放免费视频日本| 国产亚洲视频系列| 精品视频免费观看| 亚洲精品久久久久久无码色欲四季| 免费在线观看精品| 日本一区二区在线播放| 日韩av电影网| 亚洲第一网站| 欧美精品久久久久久久久久| 亚洲欧美精品久久| 精品一区电影| 亚洲欧洲一区二区三区在线观看| 岛国精品资源网站| jizz久久精品永久免费| 日韩精品一区二区三区四区| 亚洲天堂伊人网| 色狠狠一区二区三区| 91电影在线观看| 国产成人久久婷婷精品流白浆| 国产福利电影在线播放| 亚洲一区二区免费视频| wwwwww欧美| 天堂av在线电影| 一区二区三区四区不卡在线| 黄网站色视频免费观看| 日本乱理伦在线| 亚洲图片欧美综合| 免费看黄在线看| 黄视频网站在线观看| 午夜av一区二区| 欧美成人免费在线观看视频| 国产在线天堂www网在线观看| 亚洲高清不卡在线观看| 人人干视频在线| 在线成人av观看| 色狠狠色狠狠综合| 亚洲无吗一区二区三区| 色综合久久久| 日韩精品一区二区三区视频播放 | 国产成人无码精品亚洲| 亚洲精选91| 日本精品在线视频| 在线观看毛片av| 国精产品一区一区三区mba视频| 91在线高清视频| 亚洲国产视频一区二区三区| 99精品久久免费看蜜臀剧情介绍| 精品国产日本| 第九色区av在线| 亚洲人成网站色在线观看| 国产片侵犯亲女视频播放| 欧美办公室脚交xxxx| 日本韩国一区二区三区视频| 成人亚洲免费视频| 动漫3d精品一区二区三区乱码| 日韩高清中文字幕| 美女av免费看| 狠狠爱成人网| 国产精品∨欧美精品v日韩精品| 怡红院男人天堂| 国产激情视频一区二区三区欧美| 好吊妞www.84com只有这里才有精品 | 一色桃子av在线| 五月天国产精品| 91精品无人成人www| 91国内精品| 原创国产精品91| 久草网视频在线观看| 久久综合亚州| 91九色在线免费视频| 日本不卡视频一区二区| 亚洲精选视频免费看| 欧美国产激情视频| 日韩激情综合| 伊人久久综合97精品| 国产在线观看免费av| 日本美女视频一区二区| 国产伦精品一区二区| av免费观看一区二区| 天天操天天综合网| 久久出品必属精品| 成人综合久久| 欧美一级淫片aaaaaaa视频| 国产视频在线观看视频| 久久精品这里都是精品| 国产精品无码免费专区午夜| 免费成人毛片| 亚洲欧美精品一区二区| 欧美日韩一级大片| 琪琪一区二区三区| 欧美xxxx黑人又粗又长密月 | 亚洲视频在线一区二区| 成人三级视频在线播放| 欧美黄色网视频| 久久99精品久久久久久琪琪 | 亚洲天堂手机| 欧美成人在线直播| 97精品在线播放| 久久人人精品| 久久精品第九区免费观看 | 91po在线观看91精品国产性色| 国产男女猛烈无遮挡| 欧美国产日韩精品免费观看| 欧美日韩国产精品激情在线播放| 7777精品| 欧美国产日韩一区二区在线观看| 97在线视频人妻无码| 中文字幕不卡的av| 999精品视频在线| 一区三区在线欧| 清纯唯美日韩制服另类| 天堂网2014av| 天天综合色天天| 污污内射在线观看一区二区少妇 | 久久久久久国产精品久久| 99热这里只有精品66| 18欧美乱大交hd1984| 日本人69视频| 在线看片不卡| 亚洲a级在线观看| 性xxxxfjsxxxxx欧美| 欧美成人性战久久| 久草视频中文在线| av在线不卡免费看| 欧美成人精品欧美一级乱| 亚洲欧美tv| 国产精品精品久久久| 国产在线视频你懂得| 在线亚洲人成电影网站色www| 男人舔女人下部高潮全视频| 日韩精品福利网| 一本色道婷婷久久欧美| 97久久中文字幕| 久久亚洲电影天堂| 国产91免费在线观看| 亚洲成人自拍一区| 亚洲最大成人网站| 卡一卡二国产精品| 日韩一级片一区二区| 青青视频一区二区| 国产成人亚洲综合青青| 午夜老司机在线观看| 日韩三级中文字幕| 丰满少妇乱子伦精品看片| 国产亚洲短视频| 中文字幕第17页| 影音先锋日韩资源| 日韩欧美99| 免费精品一区二区三区在线观看| 久久青草精品视频免费观看| 免费在线黄色影片| 欧美片网站yy| 日本中文字幕网| 国产精品久久久久影院老司 | 波多野结衣二区三区| 国产精品麻豆99久久久久久| 国产精品二区视频| 麻豆成人在线| 男女啪啪免费观看| 免费成人高清在线视频theav| 国产精品亚洲综合天堂夜夜| 五月天激情在线| 亚洲性生活视频| 亚洲AV无码精品色毛片浪潮| 色婷婷综合在线| 免费一级肉体全黄毛片| 亚洲国产高清aⅴ视频| 国产大学生av| 老司机午夜精品99久久| 91丨porny丨探花| 99热国内精品| 日本免费高清不卡| 1204国产成人精品视频| 国产精品福利小视频| 久久香蕉一区| 日韩中文字幕精品视频| 香蕉视频成人在线| 欧美一级日韩不卡播放免费| 国产主播第一页| 亚洲h精品动漫在线观看| 波多野结衣欲乱| 91热门视频在线观看| 精品人妻无码中文字幕18禁| 卡一卡二国产精品| 免费无码av片在线观看| 韩国亚洲精品| 中文字幕在线中文字幕日亚韩一区 | 免费网站成人| 亚洲天堂网站在线观看视频| 四虎永久在线观看| 欧美一区二区三区男人的天堂| 免费视频网站在线观看入口| 午夜欧美在线一二页| 国产av无码专区亚洲av毛网站| 欧美国产激情二区三区| 成人网站免费观看| 成人av免费网站| 天堂va欧美va亚洲va老司机| 狠狠色丁香久久婷婷综合_中| 一本久道综合色婷婷五月| 99精品免费网| 男人天堂a在线| 国户精品久久久久久久久久久不卡| 一区二区成人国产精品| 日韩精品欧美激情一区二区| 日本一区二区三区四区高清视频 | 亚洲高清精品中出| 狠狠综合久久av一区二区蜜桃| 久久久水蜜桃| 亚洲a级精品| 久热国产精品视频一区二区三区| 精品av导航| 久久久久一区二区三区| 色天下一区二区三区| 久久精彩视频| 国产一区二区三区网| 亚洲精品8mav| 99久久视频| 强伦女教师2:伦理在线观看| 香蕉精品视频在线观看| 中文字幕乱码免费| 欧美另类视频| 久久综合久久网| 亚洲黄网站黄| 91视频最新入口| 久久久久网站| 自拍偷拍一区二区三区四区| 美国十次了思思久久精品导航| 五月激情婷婷在线| 国产精品99精品久久免费| 视频免费在线观看| 国产亚洲欧洲997久久综合| 久久婷婷五月综合| 中文字幕一区二区在线播放| 欧美成人精品欧美一| 亚洲第一成人在线| 亚洲s码欧洲m码国产av| 欧美无砖砖区免费| 精品久久国产视频| 亚洲精品第一页| 国产精品毛片一区二区三区四区| 手机看片国产1024| 伊人伊成久久人综合网小说| 成人黄色网址| 97视频网站入口| 电影在线观看一区二区| 91深夜福利视频| 久久aimee| 相泽南亚洲一区二区在线播放 | 精品国产依人香蕉在线精品| 97caopor国产在线视频| 77777亚洲午夜久久多人| 黄色成人在线视频| 不卡视频一区二区三区| 免费欧美激情| 日韩精品一区二区三区电影| 亚洲毛片网站| 911福利视频| 91亚洲精品久久久蜜桃| 在线观看日本黄色| 午夜天堂影视香蕉久久| 中文字幕欧美人妻精品| 亚洲成人激情图| 香蕉视频在线看| 97视频在线免费观看| 91精品视频一区二区| 久久久久久久久久久久久9999| 91久久夜色精品国产按摩| 成人免费毛片在线观看| 蜜桃视频一区二区三区| 无码任你躁久久久久久老妇| 国产精品久久久久婷婷二区次| 日韩av女优在线观看| 欧美一区在线视频| 免费一级在线观看| 欧美激情欧美激情在线五月| 激情亚洲小说| 久久婷婷人人澡人人喊人人爽| 亚洲精品91| 欧美精品aaaa| 99久久精品一区| 欧美日韩亚洲国产另类| 欧美日韩成人激情| 黄色片免费在线| 97国产suv精品一区二区62| 日韩精品视频一区二区三区| 午夜精品一区二区在线观看的 | 久久99国产成人小视频| 成人免费看片'免费看| 精品一区二区久久| 一级肉体全黄裸片| 色婷婷一区二区| 四虎电影院在线观看| 久久久久久久久网站| 4438五月综合| 亚洲日本理论电影| 日日噜噜夜夜狠狠视频欧美人| 第四色在线视频| 亚洲成人免费影院| 国产综合视频在线| 久久6免费高清热精品| 亚洲免费资源| 亚洲一区高清| 美女一区二区视频| 中文字幕精品亚洲| 欧美在线短视频| 成人在线观看网站| 国产精品久久中文| 精品国产乱码久久久| 国产无套粉嫩白浆内谢的出处| 26uuu另类欧美| 久久久久在线视频| 国产一区二区三区在线观看视频| 浪潮色综合久久天堂| 日本午夜精品一区二区三区| 日本视频一区二区三区| 国产伦精品一区二区三区视频女| 在线观看免费一区| 国产青青草在线| 欧美女优在线视频| 国产中文字幕日韩| 围产精品久久久久久久| 一区二区三区四区毛片| 亚洲免费观看高清完整版在线观看熊| 国产又大又黑又粗| 久热99视频在线观看| 亚洲成人五区| 免费拍拍拍网站| 91麻豆视频网站| 中文字幕免费观看| 神马国产精品影院av| 99精品女人在线观看免费视频 | 手机看片一区二区三区| 91精品国产高清自在线| 最新国产一区| 无限资源日本好片| 一区二区在线观看视频在线观看| 亚洲国产成人精品一区二区三区| 国内精品小视频在线观看| 亚洲精品国模| 日本美女高潮视频| 亚洲品质自拍视频网站| 成人av无码一区二区三区| 97国产精品人人爽人人做| 国产成人精品一区二区免费看京| 午夜两性免费视频| 一区二区三区四区激情| 色天堂在线视频| 国产欧美日韩中文| 极品日韩av| 精品日韩在线视频| 欧美一区二区日韩一区二区| 国产精品yjizz视频网| 视频一区免费观看| 国产91色综合久久免费分享| 日韩手机在线视频| 欧美成aaa人片免费看| 老司机精品在线| 三区视频在线观看| 狠狠躁18三区二区一区| 米奇精品一区二区三区| 激情小说网站亚洲综合网| 蜜桃av一区二区| 99久热在线精品996热是什么|