新移植的U-Boot不能正常工作,這時就需要調試了。調試U-Boot離不開工具,只有理解U-Boot啟動過程,才能正確地調試U-Boot源碼。

  

6.3.1  硬件調試器

硬件電路板制作完成以后,這時上面還沒有任何程序,就叫作裸板。首要的工作是把程序或者固件加載到裸板上,這就要通過硬件工具來完成。習慣上,這種硬件工具叫作仿真器。

仿真器可以通過處理器的JTAG等接口控制板子,直接把程序下載到目標板內存,或者進行Flash編程。如果板上的Flash是可以拔插的,就可以通過專用的Flash燒寫器來完成。在第4章介紹過目標板跟主機之間的連接,其中JTAG等接口就是專門用來連接仿真器的。

仿真器還有一個重要的功能就是在線調試程序,這對於調試Bootloader和硬件測試程序很有用。

從最簡單的JTAG電纜,到ICE仿真器,再到可以調試Linux內核的仿真器。

復雜的仿真器可以支持與計算機間的以太網或者USB接口通信。

對於U-Boot的調試,可以採用BDI2000BDI2000完全可以反匯編地跟蹤Flash中的程序,也可以進行源碼級的調試。

使用BDI2000調試U-boot的方法如下。

1)配置BDI2000和目標板初始化程序,連接目標板。

2)添加U-Boot的調試編譯選項,重新編譯。

U-Boot的程序代碼是位置相關的,調試的時候盡量在內存中調試,可以修改連接定位地址TEXT_BASETEXT_BASEboard/<board_name>/config.mk中定義。

另外,如果有復位向量也需要先從鏈接腳本中去掉。鏈接腳本是board/<board_name>/
u-boot.lds

添加調試選項,在config.mk文件中查找,DBGFLAGS,加上-g選項。然后重新編譯U-Boot

3)下載U-Boot到目標板內存。

通過BDI2000的下載命令LOAD,把程序加載到目標板內存中。然后跳轉到U-Boot入口。

4)啟動GDB調試。

啟動GDB調試,這里是交叉調試的GDBGDBBDI2000建立鏈接,然后就可以設置斷點執行了。

 

$ arm-linux-gdb u-boot

(gdb)target remote 192.168.1.100:2001

(gdb)stepi

(gdb)b start_armboot

(gdb)c

 

6.3.2  軟件跟蹤

假如U-Boot沒有任何串口打印信息,手頭又沒有硬件調試工具,那樣怎麼知道U-Boot執行到什麼地方了呢?可以通過開發板上的LED指示燈判斷。

開發板上最好設計安裝八段數碼管等LED,可以用來顯示數字或者數字位。

U-Boot可以定義函數show_boot_progress (int status),用來指示當前啟動進度。在include/common.h頭文件中聲明這個函數。

 

#ifdef CONFIG_SHOW_BOOT_PROGRESS

void    show_boot_progress (int status);

#endif

 

CONFIG_SHOW_BOOT_PROGRESS是需要定義的。這個在板子配置的頭文件中定義。CSB226開發板對這項功能有完整實現,可以參考。在頭文件include/configs/csb226.h中,有下列一行。

 

#define CONFIG_SHOW_BOOT_PROGRESS       1

 

函數show_boot_progress (int status)的實現跟開發板關系密切,所以一般在board目錄下的文件中實現。看一下CSB226board/csb226/csb226.c中的實現函數。

 

/** 設置CSB226板的0、1、2三個指示燈的開關狀態

 * csb226_set_led: - switch LEDs on or off

 * @param led:   LED to switch (0,1,2)

 * @param state: switch on (1) or off (0)

 */

void csb226_set_led(int led, int state)

{

      switch(led) {

             case 0: if (state==1) {

                              GPCR0 |= CSB226_USER_LED0;

                    } else if (state==0) {

                            GPSR0 |= CSB226_USER_LED0;

                    }

                    break;

             case 1: if (state==1) {

                              GPCR0 |= CSB226_USER_LED1;

                    } else if (state==0) {

                              GPSR0 |= CSB226_USER_LED1;

                    }

                    break;

             case 2: if (state==1) {

                              GPCR0 |= CSB226_USER_LED2;

                  } else if (state==0) {

                          GPSR0 |= CSB226_USER_LED2;

                  }

                  break;

      }

      return;

}

/** 顯示啟動進度函數,在比較重要的階段,設置三個燈為亮的狀態(1, 5, 15)*/

void show_boot_progress (int status)

{

      switch(status) {

            case  1: csb226_set_led(0,1); break;

            case  5: csb226_set_led(1,1); break;

            case 15: csb226_set_led(2,1); break;

      }

      return;

}

 

這樣,在U-Boot啟動過程中就可以通過show_boot_progresss指示執行進度。比如hang()函數是系統出錯時調用的函數,這里需要根據特定的開發板給定顯示的參數值。

 

void hang (void)

{

      puts ("### ERROR ### Please RESET the board ###\n");

#ifdef CONFIG_SHOW_BOOT_PROGRESS

      show_boot_progress(-30);

#endif

      for (;;);

}

 

6.3.3  U-Boot啟動過程

盡管有了調試跟蹤手段,甚至也可以通過串口打印信息了,但是不一定能夠判斷出錯原因。如果能夠充分理解代碼的啟動流程,那麼對準確地解決和分析問題很有幫助。

開發板上電后,執行U-Boot的第一條指令,然后順序執行U-Boot啟動函數。函數調用順序如圖6.3所示。

看一下board/smsk2410/u-boot.lds這個鏈接腳本,可以知道目標程序的各部分鏈接順序。第一個要鏈接的是cpu/arm920t/start.o,那麼U-Boot的入口指令一定位於這個程序中。下面詳細分析一下程序跳轉和函數的調用關系以及函數實現。

1cpu/arm920t/start.S

這個匯編程序是U-Boot的入口程序,開頭就是復位向量的代碼。

 

6.3  U-Boot啟動代碼流程圖

 

_start: b       reset        //復位向量

       ldr   pc, _undefined_instruction

       ldr   pc, _software_interrupt

       ldr   pc, _prefetch_abort

       ldr   pc, _data_abort

       ldr   pc, _not_used

       ldr   pc, _irq      //中斷向量

       ldr   pc, _fiq      //中斷向量

 /* the actual reset code  */

reset:          //復位啟動子程序

       /* 設置CPU為SVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

/* 關閉看門狗 */

 

/* 這些初始化代碼在系統重起的時候執行,運行時熱復位從RAM中啟動不執行 */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                       /* 把U-Boot重新定位到RAM */

       adr   r0, _start          /* r0是代碼的當前位置 */

       ldr   r1, _TEXT_BASE      /* 測試判斷是從Flash啟動,還是RAM */

       cmp     r0, r1          /* 比較r0和r1,調試的時候不要執行重定位 */

       beq     stack_setup    /* 如果r0等於r1,跳過重定位代碼 */

       /* */

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub   r2, r3, r2          /* r2 得到armboot的大小   */

       add   r2, r0, r2          /* r2 得到要復制代碼的末尾地址 */

copy_loop: /* 重新定位代碼 */

       ldmia r0!, {r3-r10}   /*從源地址[r0]復制 */

       stmia r1!, {r3-r10}   /* 復制到目的地址[r1] */

       cmp   r0, r2          /* 復制數據塊直到源數據末尾地址[r2] */

       ble   copy_loop

 

       /* 初始化堆棧等    */

stack_setup:

       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */

       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是內存分配空間 */

       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo結構體地址空間  */

#ifdef CONFIG_USE_IRQ

       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub   sp, r0, #12     /* 為abort-stack預留3個字 */

clear_bss:

       ldr   r0, _bss_start      /* 找到bss段起始地址 */

       ldr   r1, _bss_end        /*  bss段末尾地址   */

       mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]        /* bss段地址空間清零循環...  */

       add   r0, r0, #4

       cmp   r0, r1

       bne   clbss_l

       /* 跳轉到start_armboot函數入口,_start_armboot字保存函數入口指針 */

       ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函數在lib_arm/board.c中實現

/* 關鍵的初始化子程序 */

cpu_init_crit:

……  //初始化CACHE,關閉MMU等操作指令

       /* 初始化RAM時鐘。

       * 因為內存時鐘是依賴開發板硬件的,所以在board的相應目錄下可以找到memsetup.S文件。

       */

       mov   ip, lr

       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中實現

       mov   lr, ip

       mov   pc, lr

 

2lib_arm/board.c

start_armbootU-Boot執行的第一個C語言函數,完成系統初始化工作,進入主循環,處理用戶輸入的命令。

 

 

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

       /* Pointer is writable since we allocated a register for it */

       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

       /* compiler optimization barrier needed for GCC >= 3.4 */

       __asm__ __volatile__("": : :"memory");

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

       monitor_flash_len = _bss_start - _armboot_start;

       /* 順序執行init_sequence數組中的初始化函數 */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

              }

       }

       /*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start 在u-boot.lds鏈接腳本中定義 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

       /* 配置環境變量,重新定位 */

       env_relocate ();

       /* 從環境變量中獲取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       /* 以太網接口MAC 地址 */

       ……

       devices_init ();      /* 獲取列表中的設備 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制台設備 */

       enable_interrupts (); /* 使能例外處理 */

       /* 通過環境變量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop()總是試圖自動啟動,循環不斷執行 */

       for (;;) {

               main_loop ();      /* 主循環函數處理執行用戶命令 -- common/main.c */

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

3init_sequence[]

init_sequence[]數組保存著基本的初始化函數指針。這些函數名稱和實現的程序文件在下列注釋中。

 

init_fnc_t *init_sequence[] = {

       cpu_init,             /* 基本的處理器相關配置 -- cpu/arm920t/cpu.c */

       board_init,           /* 基本的板級相關配置 -- board/smdk2410/smdk2410.c */

       interrupt_init,       /* 初始化例外處理 -- cpu/arm920t/s3c24x0/interrupt.c */

       env_init,             /* 初始化環境變量 -- common/cmd_flash.c */

       init_baudrate,        /* 初始化波特率設置 -- lib_arm/board.c */

       serial_init,          /* 串口通訊設置 -- cpu/arm920t/s3c24x0/serial.c */

       console_init_f,       /* 控制台初始化階段1 -- common/console.c */

       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

       display_dram_config,  /* 顯示RAM的配置大小 -- lib_arm/board.c */

       NULL,

};

 

6.3.4  U-Boot與內核的關系

U-Boot作為Bootloader,具備多種引導內核啟動的方式。常用的gobootm命令可以直接引導內核映像啟動。U-Boot與內核的關系主要是內核啟動過程中參數的傳遞。

1go命令的實現

 

/* common/cmd_boot.c  */

int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong addr, rc;

       int     rcode = 0;

       if (argc < 2) {

              printf ("Usage:\n%s\n", cmdtp->usage);

              return 1;

       }

       addr = simple_strtoul(argv[1], NULL, 16);

       printf ("## Starting application at 0x%08lX ...\n", addr);

       /*

        * pass address parameter as argv[0] (aka command name),

        * and all remaining args

        */

       rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);

       if (rc != 0) rcode = 1;

 

       printf ("## Application terminated, rc = 0x%lX\n", rc);

       return rcode;

}

 

go命令調用do_go()函數,跳轉到某個地址執行的。如果在這個地址準備好了自引導的內核映像,就可以啟動了。盡管go命令可以帶變參,實際使用時一般不用來傳遞參數。

2bootm命令的實現

 

/* common/cmd_bootm.c */

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong iflag;

       ulong addr;

       ulong data, len, checksum;

       ulong  *len_ptr;

       uint  unc_len = 0x400000;

       int   i, verify;

       char  *name, *s;

       int   (*appl)(int, char *[]);

       image_header_t *hdr = &header;

 

       s = getenv ("verify");

       verify = (s && (*s == 'n')) ? 0 : 1;

       if (argc < 2) {

              addr = load_addr;

       } else {

              addr = simple_strtoul(argv[1], NULL, 16);

       }

       SHOW_BOOT_PROGRESS (1);

       printf ("## Booting image at %08lx ...\n", addr);

       /* Copy header so we can blank CRC field for re-calculation */

       memmove (&header, (char *)addr, sizeof(image_header_t));

       if (ntohl(hdr->ih_magic) != IH_MAGIC)

       {

              puts ("Bad Magic Number\n");

              SHOW_BOOT_PROGRESS (-1);

              return 1;

       }

       SHOW_BOOT_PROGRESS (2);

       data = (ulong)&header;

       len  = sizeof(image_header_t);

 

       checksum = ntohl(hdr->ih_hcrc);

       hdr->ih_hcrc = 0;

 

       if(crc32 (0, (char *)data, len) != checksum) {

              puts ("Bad Header Checksum\n");

              SHOW_BOOT_PROGRESS (-2);

              return 1;

       }

       SHOW_BOOT_PROGRESS (3);

       /* for multi-file images we need the data part, too */

       print_image_hdr ((image_header_t *)addr);

       data = addr + sizeof(image_header_t);

       len  = ntohl(hdr->ih_size);

       if(verify) {

              puts ("   Verifying Checksum ... ");

              if(crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

                     printf ("Bad Data CRC\n");

                     SHOW_BOOT_PROGRESS (-3);

                     return 1;

              }

              puts ("OK\n");

       }

       SHOW_BOOT_PROGRESS (4);

       len_ptr = (ulong *)data;

……

       switch (hdr->ih_os) {

       default:                /* handled by (original) Linux case */

       case IH_OS_LINUX:

             do_bootm_linux  (cmdtp, flag, argc, argv,

                         addr, len_ptr, verify);

             break;

       ……

}

 

bootm命令調用do_bootm函數。這個函數專門用來引導各種操作系統映像,可以支持引導LinuxvxWorksQNX等操作系統。引導Linux的時候,調用do_bootm_linux()函數。

3do_bootm_linux函數的實現

 

/* lib_arm/armlinux.c */

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong len = 0, checksum;

       ulong initrd_start, initrd_end;

       ulong data;

       void (*theKernel)(int zero, int arch, uint params);

       image_header_t *hdr = &header;

       bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs");

#endif

       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

       /* Check if there is an initrd image */

       if(argc >= 3) {

              SHOW_BOOT_PROGRESS (9);

              addr = simple_strtoul (argv[2], NULL, 16);

              printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

              /* Copy header so we can blank CRC field for re-calculation */

              memcpy (&header, (char *) addr, sizeof (image_header_t));

              if (ntohl (hdr->ih_magic) != IH_MAGIC) {

                      printf ("Bad Magic Number\n");

                      SHOW_BOOT_PROGRESS (-10);

                      do_reset (cmdtp, flag, argc, argv);

              }

              data = (ulong) & header;

              len = sizeof (image_header_t);

              checksum = ntohl (hdr->ih_hcrc);

              hdr->ih_hcrc = 0;

              if(crc32 (0, (char *) data, len) != checksum) {

                     printf ("Bad Header Checksum\n");

                     SHOW_BOOT_PROGRESS (-11);

                     do_reset (cmdtp, flag, argc, argv);

              }

              SHOW_BOOT_PROGRESS (10);

              print_image_hdr (hdr);

              data = addr + sizeof (image_header_t);

              len = ntohl (hdr->ih_size);

              if(verify) {

                     ulong csum = 0;

                     printf ("   Verifying Checksum ... ");

                     csum = crc32 (0, (char *) data, len);

                     if (csum != ntohl (hdr->ih_dcrc)) {

                            printf ("Bad Data CRC\n");

                            SHOW_BOOT_PROGRESS (-12);

                            do_reset (cmdtp, flag, argc, argv);

                     }

                     printf ("OK\n");

              }

              SHOW_BOOT_PROGRESS (11);

              if ((hdr->ih_os != IH_OS_LINUX) ||

                     (hdr->ih_arch != IH_CPU_ARM) ||

                     (hdr->ih_type != IH_TYPE_RAMDISK)) {

                     printf ("No Linux ARM Ramdisk Image\n");

                     SHOW_BOOT_PROGRESS (-13);

                     do_reset (cmdtp, flag, argc, argv);

              }

              /* Now check if we have a multifile image */

       } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {

               ulong tail = ntohl (len_ptr[0]) % 4;

               int i;

               SHOW_BOOT_PROGRESS (13);

               /* skip kernel length and terminator */

               data = (ulong) (&len_ptr[2]);

   &n

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 丘猴子 的頭像
    丘猴子

    轉貼部落格

    丘猴子 發表在 痞客邦 留言(0) 人氣()