為linux kernel調(diào)試增加printf

在Linux內(nèi)核調(diào)試的時(shí)候,最開始因?yàn)樵O(shè)備驅(qū)動(dòng)沒有初始化,串口也不能正常的訪問,而內(nèi)核好像也不能通過一般的Jlink調(diào)試,這個(gè)具體原因還不清楚,只是現(xiàn)象上看斷點(diǎn)停掉之后就不會(huì)繼續(xù)往下運(yùn)行(好像和之前的一個(gè)bug有點(diǎn)類似呀),先不管這些,總之我們需要有一個(gè)可以觀察內(nèi)核運(yùn)行情況的東西,嵌入式中最為常用的當(dāng)然就是printf了,雖然耗時(shí),但是簡單易用,下面就給出一個(gè)即使設(shè)備驅(qū)動(dòng)沒有初始化也能開始打印的方法。

內(nèi)核剛啟動(dòng)時(shí)候運(yùn)行匯編代碼的時(shí)候

在內(nèi)核啟動(dòng)的初期,代碼都是匯編代碼,這時(shí)候用printf好像有點(diǎn)麻煩,但是內(nèi)核代碼其實(shí)也集成了一些調(diào)試代碼,稍加修改就可以實(shí)現(xiàn)串口的打印,比如在head-common.S中有這么一個(gè)匯編函數(shù)的定義:

    arch/arm/kernel/head-common.S
    192 __error_p:
    193 #ifdef CONFIG_DEBUG_LL
    194     adr r0, str_p1
    195     bl  printascii
    196     mov r0, r9
    197     bl  printhex8
    198     adr r0, str_p2
    199     bl  printascii
    200     b   __error
    201 str_p1: .asciz  "\nError: unrecognized/unsupported processor variant (0x"
    202 str_p2: .asciz  ").\n"
    203     .align
    204 #endif
    205 ENDPROC(__error_p)

這個(gè)實(shí)際就調(diào)用了一個(gè)打印的代碼,這個(gè)里面打印了一個(gè)hex8字符,還有一個(gè)字符串,我們來看看printascii是怎么實(shí)現(xiàn)的,這個(gè)函數(shù)是在debug.S中定義的

    arch/arm/kernel/debug.S
     80 ENTRY(printascii)
     81         addruart_current r3, r1, r2
     82         b   2f
     83 1:      waituart r2, r3
     84         senduart r1, r3
     85         busyuart r2, r3
     86         teq r1, #'\n'
     87         moveq   r1, #'\r'
     88         beq 1b
     89 2:      teq r0, #0
     90         ldrneb  r1, [r0], #1
     91         teqne   r1, #0
     92         bne 1b
     93         ret lr
     94 ENDPROC(printascii)

這個(gè)函數(shù)里面調(diào)用了waituart, senduart, busyuart幾個(gè)函數(shù),這幾個(gè)函數(shù)是平臺(tái)相關(guān)的,再繼續(xù)找一下我們看到,他們的定義是在arch/arm/include/debug/stm32.S中實(shí)現(xiàn)的,

    arch/arm/include/debug/stm32.S
     12 #if defined(CONFIG_ARCH_STM32F7)
     13 # define STM32_USART_SR     (0x1C)      /* Interrupt&Status Register */
     14 # define STM32_USART_DR     (0x28)      /* Transmit Data Register */
     15 #else
     16 # define STM32_USART_SR     (0x00)      /* Status Register */
     17 # define STM32_USART_DR     (0x04)      /* Data Register */
     18 #endif
     19
     20 #define STM32_USART_SR_TXE  (1 << 7)    /* Transmitter Empty */
     21
     22     .macro  addruart, rp, rv, tmp
     23     ldr \rp, =CONFIG_DEBUG_UART_PHYS    @ phys address
     24     ldr \rv, =CONFIG_DEBUG_UART_VIRT    @ virt address
     25     .endm
     26
     27     .macro  senduart,rd,rx
     28     strb    \rd, [\rx, #(STM32_USART_DR)]   @ Write to Data Register
     29     .endm
     30
     31     .macro  waituart,rd,rx
     32 1001:   ldr \rd, [\rx, #(STM32_USART_SR)]   @ Read Status Register
     33     tst \rd, #STM32_USART_SR_TXE    @ TXE = 1 when TDR shifted
     34     beq 1001b               @ branch if TXE = 0
     35     .endm
     36
     37     .macro  busyuart,rd,rx
     38 1001:   ldr \rd, [\rx, #(STM32_USART_SR)]   @ Read Status Register
     39     tst \rd, #STM32_USART_SR_TXE    @ TXE = 0 when TDR has data
     40     beq 1001b               @ branch if TXE = 0
     41     .endm

這里面定義了和uart相關(guān)的幾個(gè)宏,這個(gè)其實(shí)也比較簡單,一個(gè)是uart的狀態(tài)寄存器,用來輪詢uart數(shù)據(jù)是否發(fā)送完成,一個(gè)是數(shù)據(jù)寄存器,用來向uart發(fā)送數(shù)據(jù),比如移植到RT1050之后,我們就可以通過修改寄存器的地址和狀態(tài)位的位移實(shí)現(xiàn)一個(gè)簡單的串口。
注意,如果需要使用匯編階段的串口需要定義CONFIG_DEBUG_LL_INCLUDE這個(gè)宏,比如:

#define CONFIG_DEBUG_LL_INCLUDE "debug/stm32.S"

這樣相應(yīng)的匯編文件會(huì)包含到debug.S中,從而實(shí)現(xiàn)調(diào)試信息的打印。
當(dāng)然還有以下兩個(gè)宏需要定義

    2542 CONFIG_DEBUG_UART_PHYS=0x40184000
    2543 CONFIG_DEBUG_UART_VIRT=0x40184000
    2544 CONFIG_DEBUG_UNCOMPRESS=y
    2545 CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
    2546 CONFIG_EARLY_PRINTK=y

完成以上修改之后就可以實(shí)現(xiàn)匯編階段的打印了。

C語言階段的打印

匯編階段的打印是比較簡單的,當(dāng)程序可以運(yùn)行c語言代碼的時(shí)候,就應(yīng)該祭出我們常用的調(diào)試?yán)鱬rintf了,在源代碼中我們發(fā)現(xiàn)內(nèi)核也貼心的實(shí)現(xiàn)了相應(yīng)的函數(shù),不過里面的Printf并不是把信息打印到串口上,而是保存在一個(gè)char buffer里面,當(dāng)串口初始化完成之后再一股腦打印出來。這個(gè)對我們的調(diào)試毫無用處呀,畢竟很多錯(cuò)誤都是在串口初始化之前出現(xiàn)的,難道要盲調(diào)?當(dāng)然不是,我們可以將這個(gè)printf稍微修改一下,加點(diǎn)代碼就可以實(shí)現(xiàn)數(shù)據(jù)到串口的打印,我們首先看看printf的具體實(shí)現(xiàn)。

    kernel/printk/printk.c
    1974 asmlinkage __visible int printk(const char *fmt, ...)
    1975 {
    1976     va_list args;
    1977     int r;
    1978
    1979     va_start(args, fmt);
    1980     r = vprintk_func(fmt, args);
    1981     va_end(args);
    1982
    1983     return r;
    1984 }
    1985 EXPORT_SYMBOL(printk);

以上是printk的定義,原諒我沒法繼續(xù)往下追,因?yàn)関printk_func = this_cpu_read(printk_func);這個(gè)一大堆的宏定義,看起來還和線程相關(guān)的,我們的目的是在初始化的時(shí)候調(diào)用這個(gè)函數(shù),和線程沒有一毛錢關(guān)系呀,看來此路不通。
偶然間想到還有一個(gè)early_printf,我們來看看這個(gè)東西是怎么一回事

    1980 #ifdef CONFIG_EARLY_PRINTK
    1981 struct console *early_console;
    1982
    1983 asmlinkage __visible void early_printk(const char *fmt, ...)
    1984 {
    1985     va_list ap;
    1986     char buf[512];
    1987     int n;
    1988
    1989     if (!early_console)
    1990         return;
    1991
    1992     va_start(ap, fmt);
    1993     n = vscnprintf(buf, sizeof(buf), fmt, ap);
    1994     va_end(ap);
    1995
    2010     early_console->write(early_console, buf, n);
    2011 }
    2012 #endif

這個(gè)就是early_printk的實(shí)現(xiàn),要使用這個(gè)函數(shù)需要定義CONFIG_EARLY_PRINTK這個(gè)宏,這個(gè)的可改造性比較強(qiáng),因?yàn)槔锩娑x了char buf,并且對各個(gè)變量進(jìn)行了處理,就是這個(gè)函數(shù)在哪里調(diào)用的我還不是特別清楚。另外early_console在哪里定義的也要研究一下。
不過n = vscnprintf(buf, sizeof(buf), fmt, ap);這個(gè)我們可以利用一下,把他用來解析printk中的格式字符串,然后將結(jié)果依次通過串口發(fā)送出去,為了簡單起見,我直接用固定地址訪問串口的寄存器,畢竟調(diào)試完了就可以刪掉了呀。

    1895 asmlinkage __visible int printk(const char *fmt, ...)
    1896 {
    1897     printk_func_t vprintk_func;
    1898     va_list args;
    1899     int r;
    1900     int n;
    1901     char buf[512];
    1902
    1903     va_start(args, fmt);
    1904
    1905     /*
    1906      * If a caller overrides the per_cpu printk_func, then it needs
    1907      * to disable preemption when calling printk(). Otherwise
    1908      * the printk_func should be set to the default. No need to
    1909      * disable preemption here.
    1910      */
    1911     vprintk_func = this_cpu_read(printk_func);
    1912     r = vprintk_func(fmt, args);
    1913     n = vscnprintf(buf, sizeof(buf), fmt, args);
    1914     va_end(args);
    1915
    1916     ////////// DEBUG by Major ////////////////
    1917     int *uart_data;
    1918     int *uart_status;
    1919     int temp;
    1920     int i;
    1921     uart_data = (int*)0x4018401c;
    1922     uart_status = (int*)0x40184014;
    1923     for(i = 0; i< n; i++){
    1924         while(!((*uart_status) & (1<<23)));
    1925         temp = (*uart_data)&0xffffff00;
    1926         *uart_data = (int)(temp|(buf[i]));
    1927     }
    1928     while(!((*uart_status) & (1<<23)));
    1929     temp = (*uart_data)&0xffffff00;
    1930     *uart_data = (int)(temp|'\r');
    1931     ////////// DEBUG by Major ////////////////
    1932
    1933
    1934     return r;
    1935 }
    1936 EXPORT_SYMBOL(printk);

以上就是改造之后的函數(shù),經(jīng)過改造之后就可以在串口上未初始化的時(shí)候打印一些必要的調(diào)試信息,并且支持格式化字符串的打印。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容