1. 程式人生 > >核心中訪問空指標(基於kernel-4.9)

核心中訪問空指標(基於kernel-4.9)

在C語言中,我們定義了NULL來表示空指標,空指標是一個特殊的指標,它其實就是0指標,*p = NULL和*p=0是等價的寫法。空指標是一個未賦值的指標,毫無意義的指標,如果訪問到該地址,那麼程式會出錯。

如果在Linux應用程式中訪問NULL指標:

會收到Segmentation Fault訊號,一般行為是該使用者程序會殺死自己,程式當然也可以捕獲對應的訊號自行處理,這種使用者態的錯誤是不會導致系統crash的。

如果是在核心中訪問到NULL指標分多種情況:

(1)如果是在核心態的程序上下文中訪問,那麼會執行oops動作,殺死當前程序,並列印相關資訊。

(2)如果是在核心態的其他上下文中(比如中斷上下文),那麼系統會執行panic動作。

(3)如果核心中有配置panic_on_oops,那麼上面發生oops的場景也會發生panic。

接下來我們來看一下程式碼中是如何執行的,當我們訪問一個頁表中不存在的地址時,那麼CPU首先會觸發一個異常,缺頁異常由硬體自動觸發。

首先我們需要看中斷向量表,當發生data abort異常時,ARM64會首先去執行entry.S中定義的:

el1_da:
......
  mov x2, sp              // struct pt_regs 
bl  do_mem_abort   //主處理函式,C程式碼
......
el0_da:
/*                                                         
 * Data abort handling                                     
 */
mrs x26, far_el1 // enable interrupts before calling the main handler enable_dbg_and_irq ct_user_exit clear_address_tag x0, x26 mov x1, x25 mov x2, sp bl do_mem_abort //主處理函式,C程式碼
b ret_to_user

下面我們以此跟蹤下去,可以看到會執行到arch/arm64/mm/fault.c:


/*
 * Dispatch a data abort to the relevant handler.
 */
asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
                     struct pt_regs *regs)
{
    const struct fault_info *inf = esr_to_fault_info(esr); //根據esr找到對應的fault_info陣列成員
    struct siginfo info;
    if (!inf->fn(addr, esr, regs))
        return;
    pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx\n",
         inf->name, esr, addr);
    info.si_signo = inf->sig;
    info.si_errno = 0;
    info.si_code  = inf->code;
    info.si_addr  = (void __user *)addr;
    arm64_notify_die("", regs, &info, esr);
}
 static inline const struct fault_info *esr_to_fault_info(unsigned int esr)
 {
     return fault_info + (esr & 63);
 }       

下面來看fault info陣列的定義:

static const struct fault_info fault_info[] = {
    { do_bad,       SIGBUS,  0,     "ttbr address size fault"   },
    { do_bad,       SIGBUS,  0,     "level 1 address size fault"    },
    { do_bad,       SIGBUS,  0,     "level 2 address size fault"    },
    { do_bad,       SIGBUS,  0,     "level 3 address size fault"    },
    { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 0 translation fault" },
    { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 1 translation fault" },
    { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 2 translation fault" },
    { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 3 translation fault" },
    { do_bad,       SIGBUS,  0,     "unknown 8"         },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 1 access flag fault" },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 2 access flag fault" },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 3 access flag fault" },
    { do_bad,       SIGBUS,  0,     "unknown 12"            },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 1 permission fault"  },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 2 permission fault"  },
    { do_page_fault,    SIGSEGV, SEGV_ACCERR,   "level 3 permission fault"  },
    { do_bad,       SIGBUS,  0,     "synchronous external abort"    },
    { do_bad,       SIGBUS,  0,     "unknown 17"            },
    { do_bad,       SIGBUS,  0,     "unknown 18"            },
    { do_bad,       SIGBUS,  0,     "unknown 19"            },
    { do_bad,       SIGBUS,  0,     "synchronous external abort (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous external abort (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous external abort (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous external abort (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous parity error"  },
    { do_bad,       SIGBUS,  0,     "unknown 25"            },
    { do_bad,       SIGBUS,  0,     "unknown 26"            },
    { do_bad,       SIGBUS,  0,     "unknown 27"            },
    { do_bad,       SIGBUS,  0,     "synchronous parity error (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous parity error (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous parity error (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "synchronous parity error (translation table walk)" },
    { do_bad,       SIGBUS,  0,     "unknown 32"            },
    { do_alignment_fault,   SIGBUS,  BUS_ADRALN,    "alignment fault"       },
    { do_bad,       SIGBUS,  0,     "unknown 34"            },
    { do_bad,       SIGBUS,  0,     "unknown 35"            },
    { do_bad,       SIGBUS,  0,     "unknown 36"            },
    { do_bad,       SIGBUS,  0,     "unknown 37"            },
    { do_bad,       SIGBUS,  0,     "unknown 38"            },
    { do_bad,       SIGBUS,  0,     "unknown 39"            },
    { do_bad,       SIGBUS,  0,     "unknown 40"            },
    { do_bad,       SIGBUS,  0,     "unknown 41"            },
    { do_bad,       SIGBUS,  0,     "unknown 42"            },
    { do_bad,       SIGBUS,  0,     "unknown 43"            },
    { do_bad,       SIGBUS,  0,     "unknown 44"            },
    { do_bad,       SIGBUS,  0,     "unknown 45"            },
    { do_bad,       SIGBUS,  0,     "unknown 46"            },
    { do_bad,       SIGBUS,  0,     "unknown 47"            },
    { do_tlb_conf_fault,    SIGBUS,  0,     "TLB conflict abort"        },
    { do_bad,       SIGBUS,  0,     "unknown 49"            },
    { do_bad,       SIGBUS,  0,     "unknown 50"            },
    { do_bad,       SIGBUS,  0,     "unknown 51"            },
    { do_bad,       SIGBUS,  0,     "implementation fault (lockdown abort)" },
    { do_bad,       SIGBUS,  0,     "implementation fault (unsupported exclusive)" },
    { do_bad,       SIGBUS,  0,     "unknown 54"            },
    { do_bad,       SIGBUS,  0,     "unknown 55"            },
    { do_bad,       SIGBUS,  0,     "unknown 56"            },
    { do_bad,       SIGBUS,  0,     "unknown 57"            },
    { do_bad,       SIGBUS,  0,     "unknown 58"            },
    { do_bad,       SIGBUS,  0,     "unknown 59"            },
    { do_bad,       SIGBUS,  0,     "unknown 60"            },
    { do_bad,       SIGBUS,  0,     "section domain fault"      },
    { do_bad,       SIGBUS,  0,     "page domain fault"     },
    { do_bad,       SIGBUS,  0,     "unknown 63"            },
};

上面定義的fault info,主要關鍵的處理函式就如下幾個,分別是:

do_bad/do_alignment_fault/do_tlb_conf_fault/do_translation_fault/do_page_fault。

回到最初的問題,如果我們想要訪問的是指標0地址,那麼硬體會執行到do_page_fault這個函式中來進行處理。我們來繼續跟蹤一下,看看這個函式具體做了什麼吧。

static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,
                   struct pt_regs *regs)
{
    struct task_struct *tsk;
    struct mm_struct *mm;
    int fault, sig, code;
    unsigned long vm_flags = VM_READ | VM_WRITE;
    unsigned int mm_flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
    if (notify_page_fault(regs, esr))
        return 0;
    tsk = current;
    mm  = tsk->mm;
    /*
     * If we're in an interrupt or have no user context, we must not take
     * the fault.
     */
if (faulthandler_disabled() || !mm) //faulthandler_disabled()函式判斷是否faulthandler被disabled了或者是否處於中斷上下文
                                     //!mm用來判斷是否沒有使用者程序的上下文,如果都處於中斷上下文或者沒有使用者上下文,那麼直接跳轉
        goto no_context;
    if (user_mode(regs))
        mm_flags |= FAULT_FLAG_USER;
    if (is_el0_instruction_abort(esr)) {
        vm_flags = VM_EXEC;
    } else if (((esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM)) ||
            ((esr & ESR_ELx_CM) && !(mm_flags & FAULT_FLAG_USER))) {
        vm_flags = VM_WRITE;
        mm_flags |= FAULT_FLAG_WRITE;
    }
    if (addr < USER_DS && is_permission_fault(esr, regs)) {
        /* regs->orig_addr_limit may be 0 if we entered from EL0 */
        if (regs->orig_addr_limit == KERNEL_DS)
            die("Accessing user space memory with fs=KERNEL_DS", regs, esr);
        if (is_el1_instruction_abort(esr))
            die("Attempting to execute userspace memory", regs, esr);
        if (!search_exception_tables(regs->pc))
            die("Accessing user space memory outside uaccess.h routines", regs, esr);
    }
    /*
     * As per x86, we may deadlock here. However, since the kernel only
     * validly references user space from well defined areas of the code,
     * we can bug out early if this is from code which shouldn't.
     */
    if (!down_read_trylock(&mm->mmap_sem)) {
        if (!user_mode(regs) && !search_exception_tables(regs->pc))
            goto no_context;
retry:
        down_read(&mm->mmap_sem);
    } else {
        /*
         * The above down_read_trylock() might have succeeded in which
         * case, we'll have missed the might_sleep() from down_read().
         */
        might_sleep();
#ifdef CONFIG_DEBUG_VM
        if (!user_mode(regs) && !search_exception_tables(regs->pc))
            goto no_context;
#endif
    }
    fault = __do_page_fault(mm, addr, mm_flags, vm_flags, tsk);
    /*
     * If we need to retry but a fatal signal is pending, handle the
     * signal first. We do not need to release the mmap_sem because it
     * would already be released in __lock_page_or_retry in mm/filemap.c.
     */
    if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) {
        if (!user_mode(regs))
            goto no_context;
        return 0;
    }
    /*
     * Major/minor page fault accounting is only done on the initial
     * attempt. If we go through a retry, it is extremely likely that the
     * page will be found in page cache at that point.
     */
    perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
    if (mm_flags & FAULT_FLAG_ALLOW_RETRY) {
        if (fault & VM_FAULT_MAJOR) {
            tsk->maj_flt++;
            perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, regs,
                      addr);
        } else {
            tsk->min_flt++;
            perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, regs,
                      addr);
        }
        if (fault & VM_FAULT_RETRY) {
            /*
             * Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk of
             * starvation.
             */
            mm_flags &= ~FAULT_FLAG_ALLOW_RETRY;
            mm_flags |= FAULT_FLAG_TRIED;
            goto retry;
        }
    }
    up_read(&mm->mmap_sem);
    /*
     * Handle the "normal" case first - VM_FAULT_MAJOR
     */
    if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP |
                  VM_FAULT_BADACCESS))))
        return 0;
    /*
     * If we are in kernel mode at this point, we have no context to
     * handle this fault with.
     */
    if (!user_mode(regs))
        goto no_context;
    if (fault & VM_FAULT_OOM) {
        /*
         * We ran out of memory, call the OOM killer, and return to
         * userspace (which will retry the fault, or kill us if we got
         * oom-killed).
         */
        pagefault_out_of_memory();
        return 0;
    }
    if (fault & VM_FAULT_SIGBUS) {
        /*
         * We had some memory, but were unable to successfully fix up
         * this page fault.
         */
        sig = SIGBUS;
        code = BUS_ADRERR;
    } else {
        /*
         * Something tried to access memory that isn't in our memory
         * map.
         */
        sig = SIGSEGV;
        code = fault == VM_FAULT_BADACCESS ?
            SEGV_ACCERR : SEGV_MAPERR;
    }
    __do_user_fault(tsk, addr, esr, sig, code, regs); //在使用者程序上下文中發現地址無法被對映,比如地址0,那麼就會執行到此處,do user fault
    return 0;
no_context:   //沒有程序上下文的情況會直接跳到此執行do kernel fault
    __do_kernel_fault(mm, addr, esr, regs);
    return 0;
}

此函式的中間部分都是進行缺頁異常的處理,比如重新建立頁表,從夥伴系統分配記憶體等等,我們暫且不去進一步分析。只看最後出現異常的情況,異常分兩種,一種是有使用者上下文,一種沒有使用者上下文,分別會去執行__do_user_fault和__do_kernel_fault。這不就是和我們前面的描述,兩種訪問0地址的方式場景匹配了嘛。

/*
 * Something tried to access memory that isn't in our memory map. User mode
 * accesses just cause a SIGSEGV
 */
static void __do_user_fault(struct task_struct *tsk, unsigned long addr,
                unsigned int esr, unsigned int sig, int code,
                struct pt_regs *regs)
{
    struct siginfo si;
    const struct fault_info *inf;
    trace_user_fault(tsk, addr, esr);
    if (unhandled_signal(tsk, sig) && show_unhandled_signals_ratelimited()) {
        inf = esr_to_fault_info(esr);
        pr_info("%s[%d]: unhandled %s (%d) at 0x%08lx, esr 0x%03x\n",
            tsk->comm, task_pid_nr(tsk), inf->name, sig,
            addr, esr);
        show_pte(tsk->mm, addr);
        show_regs(regs);
    }
    tsk->thread.fault_address = addr;
    tsk->thread.fault_code = esr;
    si.si_signo = sig;
    si.si_errno = 0;
    si.si_code = code;
    si.si_addr = (void __user *)addr;
    force_sig_info(sig, &si, tsk);
}
/*
 * The kernel tried to access some page that wasn't present.
 */
static void __do_kernel_fault(struct mm_struct *mm, unsigned long addr,
                  unsigned int esr, struct pt_regs *regs)
{
    /*
     * Are we prepared to handle this kernel fault?
     * We are almost certainly not prepared to handle instruction faults.
     */
    if (!is_el1_instruction_abort(esr) && fixup_exception(regs))
        return;
    /*
     * No handler, we'll have to terminate things with extreme prejudice.
     */
    bust_spinlocks(1);
    pr_alert("Unable to handle kernel %s at virtual address %08lx\n",
         (addr < PAGE_SIZE) ? "NULL pointer dereference" :
         "paging request", addr);
    show_pte(mm, addr);
    die("Oops", regs, esr);
    bust_spinlocks(0);
    do_exit(SIGKILL);
}

相關推薦

核心訪問指標基於kernel-4.9

在C語言中,我們定義了NULL來表示空指標,空指標是一個特殊的指標,它其實就是0指標,*p = NULL和*p=0是等價的寫法。空指標是一個未賦值的指標,毫無意義的指標,如果訪問到該地址,那麼程式會出錯。 如果在Linux應用程式中訪問NULL指標: 會收到

ARM64核心系統呼叫詳解基於kernel-4.9

本文以ARM64為例,介紹如何新增系統呼叫,首先來介紹一些程式碼執行流程: 首先來看異常向量表的配置,核心在arch/arm64/kernel/entry.S彙編程式碼中設定了異常向量表。 /* * Exception vectors. */

用pandas或numpy處理資料np.isnan()/pd.isnull()

最近在做資料處理的時候,遇到個讓我欲仙欲死的問題,那就是資料中的空值該如何獲取。 我的目的本來是獲取資料中的所有非零且非空值,然後再計算獲得到的所有資料計算均值,再用均值把0和空值填上。這個操作讓我意識到了i is None/np.isnan(i)/i.isnull()之間的差別,再此

Python爬取百度貼吧回帖的微訊號基於簡單http請求

作者:草小誠 轉載請注原文地址:https://blog.csdn.net/cxcjoker7894/article/details/85685115 前些日子媳婦兒有個需求,想要一個任意貼吧近期主題帖的所有回帖中的微訊號,用來做一些微商的操作,你懂的。因為有些貼吧專門就是

樹莓派3B+ 原始碼方式安裝opencv3基於3.4.1

身邊有朋友在樹莓派上安裝不上去opencv3,因此在這裡記錄了一下自己安裝opencv3的過程。 這位前輩的安裝步驟非常非常詳細,下邊所有過程都是參考此經驗,只不過添加了自己安裝過程的圖片,看著好理解一點。 非常感謝,謝謝謝謝謝謝! 安裝過程 更換軟體源的時候建議

Opencv庫組成以及主要檔案作用版本2.4.9

                                                                             opencv主要資料夾構成(版本2.4.

機器學習算法的評價指標準確率、召回率、F值、ROC、AUC等

html eight inf 曲線 mba cor 方法 指標 pan 參考鏈接:https://www.cnblogs.com/Zhi-Z/p/8728168.html 具體更詳細的可以查閱周誌華的西瓜書第二章,寫的非常詳細~ 一、機器學習性能評估指標 1.準確率(A

機器學習演算法的評價指標準確率、召回率、F值、ROC、AUC等

參考連結:https://www.cnblogs.com/Zhi-Z/p/8728168.html 具體更詳細的可以查閱周志華的西瓜書第二章,寫的非常詳細~ 一、機器學習效能評估指標 1.準確率(Accurary)   準確率是我們最常見的評價指標,而且很容易理解,就是被分對的樣本

Linux核心--01基於armA9tiny4412開發板

1、如何編譯核心 tar -Jxvf linux-3.5-20170929.tar.xz -C /~ 解壓核心壓縮包至自己的家目錄 建議刪除arch目錄與我們無關的其他架構的資料夾 通過./config生成Makefile,但是因為我們初學核心,不懂得用 m

java 指標,不為,的理解

一、null是代表不確定的物件 Java中,null是一個關鍵字,用來標識一個不確定的物件。因此可以將null賦給引用型別變數,但不可以將null賦給基本型別變數。 比如:int a = null;是錯誤的。Ojbect o = null是正確的。 Java中,變數的

擁抱PBO基於專案的組織聚焦核心價值創造

近年來,PBO(Project-Based Organizations)作為一種新興的整合各類專業智力資源和專業知識的組織結構,受到越來越多的關注,第五版PMBOK出現的新詞彙,三種組織(職能型、矩陣型、專案型)都可以建立PBO,以減輕“等級制度和衙門主義”對專案的不良影響

Quartz和Spring,Mybatis結合,讀資料庫指標NullPointerException

專案中要用定時任務,採用的是Quartz,配置好了可以跑定時了,但是在讀資料庫的時候報空指標,注入的mapper介面類沒有獲取到,費了點時間找到了原因,在這裡記錄下。 我的Quartz是這麼配置的 &

關於微信小程式圖示的引用基於iconfont

步驟如下: iconfont圖示程式碼下載 在該網站中尋找我們需要的圖示,加入購物車(注意是加入購物車哦。如圖) 【加入購物車】 【檢視購物車,點選下載程式碼】 【下載後的檔案,解壓】 關注解壓檔案 【iconfont.ttf】和【

c++使用指標呼叫成員函式的理解

使用空指標呼叫成員函式會如何? 舉個例子:base是基類,裡面有兩個函式:non-virtual func2 以及 virtual func1; derived是派生類,使用public繼承自base,裡面有四個函式:virtual func1,non-vi

C++Reference與指標Pointer的使用對比

reference VS pointer 1、 定義:                                               與pointer 類似,一個reference是一個物件(object),可以用來間接指向另一個物件。 一個reference

SQLnull對算術運算、比較運算、集合運算的影響

算術運算如果算術表示式的任一輸入為空,則該算術表示式(涉及諸如 +、-、* 或 / 的算術運算)結果為空。例:如果查詢中有一個表示式是r.A + 5, 並且對於關係中某個特定的元組, r.A為空,那麼對此元組來說,該表示式的結果也為空。比較運算SQL將涉及空值的任何比較運算的

java 程式設計遇到指標異常的可能原因java.lang.nullpointerexception

1.所謂的指標,就是java中的物件的引用。比如String s;這個s就是指標。 2.所謂的空指標,就是指標的內容為空,比如上面的s,如果令它指向null,就是空指標。 3.所謂的空指標異常,就是一個指標是空指標,你還要去操作它,既然它指向的是空物件,它就不能使用這個物件

資料結構佇列的實現基於順序表迴圈佇列

    在學習資料結構中,佇列也是一個重要的資料結構,我們今天來用基於順序表的佇列(Queue),在基於順序表佇列如果是不迴圈的順序表,則在出佇列時,時間複雜度是O(n),所以我們用迴圈佇列來實現,怎麼解釋基於迴圈順序表的佇列呢?我們上圖: 上圖是在不迴圈順序表中出隊。這樣不

JS去除陣列字串空格也清除

$scope.gop.pictTypeArr = $.grep($scope.gop.pictTypeArr, function (x) { return $.trim(x).length

Python關於URL的處理基於Python2.7版本

參考官方文件:https://docs.python.org/3/library/urllib.html點選開啟連結1、 完整的url語法格式: 協議://使用者名稱@密碼:子域名.域名.頂級域名:埠