精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

www.国产com| 成人做爰69片免费| 免费看a在线观看| 国产精品一品二品| 久久久伊人欧美| 中文字幕一区二区三区人妻不卡| 韩漫成人漫画| 日韩一区在线播放| 高清不卡日本v二区在线| 中文字幕精品无码一区二区| 国产精品99久久精品| 欧美精品一区男女天堂| 搡女人真爽免费午夜网站| 女人黄色免费在线观看| 久久久国产一区二区三区四区小说| 成人羞羞国产免费| 国产成人综合欧美精品久久| 欧美亚洲国产精品久久| 精品乱人伦小说| 免费一级特黄录像| 手机在线理论片| 亚洲精品亚洲人成人网在线播放| 久久久人人爽| 亚洲免费一级片| 久久机这里只有精品| 91国产一区在线| 亚洲色婷婷一区二区三区| 精品久久久久中文字幕小说 | 欧美二区在线视频| 日韩专区在线| 欧美激情一区二区在线| 精品视频第一区| 国产福利资源在线| 激情深爱一区二区| 国产精品久久久久久久电影| 国产成人一区二区三区影院在线 | av在线播放天堂| 久cao在线| 欧美经典一区二区三区| 精品无人区一区二区三区| 亚洲福利在线观看视频| 激情深爱一区二区| 国产久一一精品| 青青国产在线视频| 午夜亚洲影视| 欧美一级大片在线观看| 国产一级视频在线| 欧美黄色精品| 999国产精品永久免费视频app| 久久久久久亚洲综合| 7777精品久久久大香线蕉小说| 天天干,天天干| 久久狠狠婷婷| 4444欧美成人kkkk| 亚洲黄色三级视频| 在线成人欧美| 久久久免费精品视频| 久久免费看少妇高潮v片特黄 | 亚洲国产精品一区二区三区| 日本成人在线免费| 91麻豆精品激情在线观看最新| 91精品国产综合久久精品性色| 福利片一区二区三区| 亚洲欧洲二区| 欧美一级二级三级蜜桃| 久久久无码人妻精品无码| 视频一区国产| 精品sm捆绑视频| 日韩精品视频一区二区| 亚洲另类春色校园小说| 亚洲视频免费一区| 99在线视频免费| 婷婷综合久久| 欧美日本啪啪无遮挡网站| 久久久久久久久久久网 | 久久久久久久午夜| www.日韩| 欧美日韩在线三级| 夜夜爽久久精品91| 国产一区在线电影| 亚洲欧美制服丝袜| 免费精品在线视频| 激情文学一区| 欧美孕妇性xx| 亚洲视频在线观看免费视频| 国产乱人伦精品一区二区在线观看| 亚洲综合大片69999| 日本毛片在线观看| 国产日产亚洲精品系列| 无码毛片aaa在线| 小早川怜子影音先锋在线观看| 日本韩国欧美在线| 国产美女视频免费看| 激情小说一区| 尤物yw午夜国产精品视频明星| 我要看黄色一级片| 野花国产精品入口| 国产精品男人的天堂| 成人毛片在线精品国产| 国产亚洲欧美在线| 欧美精品在欧美一区二区| 成人爽a毛片免费啪啪| 91精品国产色综合久久| 中文字幕乱码在线| 999久久久精品国产| 91国内在线视频| 97超碰人人草| 337p粉嫩大胆噜噜噜噜噜91av| 一卡二卡3卡四卡高清精品视频| 中文字幕在线观看网站| 91黄色免费观看| 亚洲国产精品第一页| 大色综合视频网站在线播放| 国精产品一区一区三区有限在线| 青娱乐在线免费视频| 成人激情免费网站| 天天爱天天做天天操| 日韩欧美看国产| 精品国产一区二区在线观看| 欧美一级特黄高清视频| 久久久水蜜桃av免费网站| av成人观看| 日韩黄色影院| 欧美天堂亚洲电影院在线播放| 久久人妻少妇嫩草av无码专区| 国产精品成人a在线观看| 国产成人精品免费视频| 五月婷婷六月色| 综合中文字幕亚洲| 孩娇小videos精品| 久久不见久久见国语| 国模精品系列视频| 国内老熟妇对白xxxxhd| 中文字幕一区二区不卡| 少妇激情一区二区三区| 亚洲免费毛片| 91高清视频免费| 涩涩视频免费看| 亚洲永久免费视频| 成人三级做爰av| 亚洲草久电影| 91最新国产视频| a黄色在线观看| 在线观看亚洲精品视频| 一区二区黄色片| 国产午夜精品一区二区三区欧美| 国产精品视频免费观看| 国模雨婷捆绑高清在线| 日韩精品一区二区三区在线| caoporn91| 国产精品91xxx| 精品国产一区二区三区在线| 精品国产一区二| 欧美久久久精品| www国产一区| 亚洲综合免费观看高清完整版| 日本一本在线视频| 欧美日韩精品| 粉嫩av一区二区三区免费观看| 日本在线视频www鲁啊鲁| 日韩美女一区二区三区四区| 免费在线观看亚洲| 不卡av免费在线观看| 玩弄中年熟妇正在播放| 图片婷婷一区| 国产成人精品国内自产拍免费看| av资源网站在线观看| 欧美日韩久久久| 国精品无码一区二区三区| 岛国一区二区三区| www一区二区www免费| 欧美激情在线精品一区二区三区| 欧美综合激情网| 在线视频自拍| 日韩亚洲欧美成人一区| 日韩精品乱码久久久久久| 久久色在线观看| 亚洲免费看av| 欧美日韩精品| 久久本道综合色狠狠五月| 91大神在线观看线路一区| 久久精品国产一区二区电影| 亚洲精品久久久久久动漫器材一区 | 国产日韩中文在线| 色在线视频网| 亚洲欧美成人在线| 国产精品一区二区免费视频| 亚洲国产中文字幕| 欧美熟妇激情一区二区三区| 国产一区二区影院| 大j8黑人w巨大888a片| 日韩在线视屏| 国产一区二区三区四区五区加勒比| 自拍网站在线观看| 超在线视频97| 欧美另类自拍| 337p亚洲精品色噜噜狠狠| 日本在线视频免费观看| 亚洲欧洲韩国日本视频| 人妻丰满熟妇av无码久久洗澡| 奇米777欧美一区二区| 欧美日韩不卡在线视频| 清纯唯美日韩| 国产另类第一区| 日本久久久久| 欧美亚洲视频在线看网址| 国产精品扒开做爽爽爽的视频| 日韩高清人体午夜| 国产精品久久久久毛片| 色哟哟亚洲精品| 麻豆国产尤物av尤物在线观看| 国产婷婷色一区二区三区| 激情小说欧美色图| 久久国产精品99久久久久久老狼| 国产精品裸体瑜伽视频| 你懂的国产精品永久在线| 欧美一级片免费观看| 88久久精品| 成人福利视频网| 欧美羞羞视频| 91av视频导航| 成全电影大全在线观看| 久久久国产精品x99av | 久久伊人色综合| 激情福利在线| 日韩av一区二区在线观看| av官网在线观看| 欧美日韩成人综合天天影院| 国产免费av一区| 亚洲大片免费看| 青青草原在线免费观看| 国产精品女人毛片| 第一次破处视频| 久久久亚洲精品一区二区三区| a级片在线观看视频| 国产乱码精品一区二区三区五月婷| 九九九在线观看视频| 日韩精品乱码av一区二区| 黄色片视频在线免费观看| 一本色道久久综合亚洲精品高清| 嫩草影院中文字幕| 欧美成熟视频| 欧美另类videos| 一区二区三区午夜视频| 欧美三级午夜理伦三级老人| 亚洲女同中文字幕| 中文字幕在线乱| 欧美一区二区三区久久精品茉莉花| 一区二区免费在线观看| 国产精品久久久久久麻豆一区软件| 性欧美.com| 欧美三级美国一级| 亚洲国产精品一区二区第一页| 欧美限制电影| 一级全黄肉体裸体全过程| 久久久久蜜桃| 国产又粗又大又爽的视频| 欧美人成网站| 免费人成在线观看视频播放| 精品二区视频| 精品人妻少妇一区二区| 中文一区二区| 免费国产成人av| 美女www一区二区| 亚洲精品在线视频播放| 国产成人综合在线播放| 污片免费在线观看| 久久这里只有精品6| 亚洲一区 欧美| 亚洲男人的天堂一区二区| 我要看黄色一级片| 亚洲成av人片一区二区| 亚洲 欧美 成人| 欧美日韩免费观看一区二区三区| 国产精品一级视频| 亚洲国产成人精品女人久久久| 爽爽视频在线观看| 色综合伊人色综合网站| 3d玉蒲团在线观看| 91精品国产91久久久久福利| 欧美aaa视频| 92国产精品久久久久首页| 老牛精品亚洲成av人片| 天天爽天天狠久久久| 欧美暴力喷水在线| av黄色在线网站| 蜜臀久久99精品久久久画质超高清 | 国产精品对白刺激久久久| 亚洲综合图色| 大桥未久一区二区三区| 亚洲专区欧美专区| 色18美女社区| 久久亚洲一区二区三区明星换脸| 成人在线一级片| 一区二区三区中文在线观看| 永久免费无码av网站在线观看| 91精品国产综合久久香蕉麻豆| 欧美一区,二区| 日韩网站免费观看| 绿色成人影院| 97超碰人人看人人 | 国产欧美综合一区| 午夜在线精品偷拍| 免费国偷自产拍精品视频| 久久中文字幕电影| 久久久久无码国产精品不卡| 欧美无砖专区一中文字| 高h调教冰块play男男双性文| 国产一区二区三区在线看| 日本欧美电影在线观看| 国产自产女人91一区在线观看| 欧美理伦片在线播放| 成年人黄色在线观看| 丝袜a∨在线一区二区三区不卡| 亚洲精品久久久久久| 国产精品毛片大码女人| 国产又大又黄视频| 日韩视频免费观看高清完整版 | 91视频你懂的| 玖玖爱免费视频| 制服丝袜激情欧洲亚洲| 国产免费av在线| 97视频网站入口| 懂色av色香蕉一区二区蜜桃| 日本一区二区精品视频| 国产精品嫩草99av在线| 精品人妻在线视频| 亚洲精品网站在线观看| 国产一区二区三区视频免费观看| 亚洲天堂av在线免费观看| 草草在线观看| 国产欧美一区二区视频| 合欧美一区二区三区| 国产老头和老头xxxx×| 18成人在线视频| 一级片免费网站| 日韩中文字幕在线看| 免费日韩成人| 亚洲精品国产精品国自产观看| 老司机亚洲精品| 永久免费看mv网站入口78| 福利微拍一区二区| 色天堂在线视频| 欧美性资源免费| 天海翼亚洲一区二区三区| 国产综合av在线| 99re视频这里只有精品| 97免费在线观看视频| 亚洲激情电影中文字幕| 白浆在线视频| 久久影院理伦片| 久久久久久久欧美精品| 性猛交ⅹxxx富婆video| 欧美日韩一区二区三区高清| 9色在线视频网站| 国产欧美日韩高清| 国产精品国产一区| 爱情岛论坛亚洲自拍| 一卡二卡欧美日韩| 欧美一区二区公司| 欧美亚洲一区在线| 成人情趣视频网站| 亚洲综合123| 一区二区国产视频| 西西人体44www大胆无码| 国产成人在线一区二区| 色综合色综合| 小日子的在线观看免费第8集| 亚洲成人自拍网| 免费资源在线观看| 成人国产精品日本在线| 欧美黄色一区| av网站免费在线播放| 欧美性生活一区| 1024在线播放| 久久av免费一区| 另类调教123区| 久久精品www人人爽人人| 日韩高清中文字幕| 深夜日韩欧美| 你真棒插曲来救救我在线观看| 91麻豆swag| 国产女主播福利| 91国内免费在线视频| 国产精品国产三级国产在线观看| 亚洲精品成人无码毛片| 色婷婷久久久综合中文字幕| 理论片午午伦夜理片在线播放| 99在线影院| 蜜桃视频一区二区三区| 国产一国产二国产三| 影音先锋日韩有码| 精品三级av在线导航| 免费涩涩18网站入口| 亚洲国产精品一区二区久久 | 久久久久高清精品| 国产日韩欧美一区二区东京热| 午夜精品三级视频福利| 欧美电影一二区| 91av在线免费|