新移植的U-Boot不能正常工作,這時就需要調試了。調試U-Boot離不開工具,只有理解U-Boot啟動過程,才能正確地調試U-Boot源碼。
硬件電路板制作完成以后,這時上面還沒有任何程序,就叫作裸板。首要的工作是把程序或者固件加載到裸板上,這就要通過硬件工具來完成。習慣上,這種硬件工具叫作仿真器。 仿真器可以通過處理器的JTAG等接口控制板子,直接把程序下載到目標板內存,或者進行Flash編程。如果板上的Flash是可以拔插的,就可以通過專用的Flash燒寫器來完成。在第4章介紹過目標板跟主機之間的連接,其中JTAG等接口就是專門用來連接仿真器的。 仿真器還有一個重要的功能就是在線調試程序,這對於調試Bootloader和硬件測試程序很有用。 從最簡單的JTAG電纜,到ICE仿真器,再到可以調試Linux內核的仿真器。 復雜的仿真器可以支持與計算機間的以太網或者USB接口通信。 對於U-Boot的調試,可以採用BDI2000。BDI2000完全可以反匯編地跟蹤Flash中的程序,也可以進行源碼級的調試。 使用BDI2000調試U-boot的方法如下。 (1)配置BDI2000和目標板初始化程序,連接目標板。 (2)添加U-Boot的調試編譯選項,重新編譯。 U-Boot的程序代碼是位置相關的,調試的時候盡量在內存中調試,可以修改連接定位地址TEXT_BASE。TEXT_BASE在board/<board_name>/config.mk中定義。 另外,如果有復位向量也需要先從鏈接腳本中去掉。鏈接腳本是board/<board_name>/ 添加調試選項,在config.mk文件中查找,DBGFLAGS,加上-g選項。然后重新編譯U-Boot。 (3)下載U-Boot到目標板內存。 通過BDI2000的下載命令LOAD,把程序加載到目標板內存中。然后跳轉到U-Boot入口。 (4)啟動GDB調試。 啟動GDB調試,這里是交叉調試的GDB。GDB與BDI2000建立鏈接,然后就可以設置斷點執行了。 $ arm-linux-gdb u-boot (gdb)target remote 192.168.1.100:2001 (gdb)stepi (gdb)b start_armboot (gdb)c 假如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目錄下的文件中實現。看一下CSB226在board/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 (;;); } 盡管有了調試跟蹤手段,甚至也可以通過串口打印信息了,但是不一定能夠判斷出錯原因。如果能夠充分理解代碼的啟動流程,那麼對準確地解決和分析問題很有幫助。 開發板上電后,執行U-Boot的第一條指令,然后順序執行U-Boot啟動函數。函數調用順序如圖6.3所示。 看一下board/smsk2410/u-boot.lds這個鏈接腳本,可以知道目標程序的各部分鏈接順序。第一個要鏈接的是cpu/arm920t/start.o,那麼U-Boot的入口指令一定位於這個程序中。下面詳細分析一下程序跳轉和函數的調用關系以及函數實現。 這個匯編程序是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 start_armboot是U-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 */ } 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, }; U-Boot作為Bootloader,具備多種引導內核啟動的方式。常用的go和bootm命令可以直接引導內核映像啟動。U-Boot與內核的關系主要是內核啟動過程中參數的傳遞。 /* 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命令可以帶變參,實際使用時一般不用來傳遞參數。 /* 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函數。這個函數專門用來引導各種操作系統映像,可以支持引導Linux、vxWorks、QNX等操作系統。引導Linux的時候,調用do_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]); &n6.3.1 硬件調試器
u-boot.lds。6.3.2 軟件跟蹤
6.3.3 U-Boot啟動過程
1.cpu/arm920t/start.S
2.lib_arm/board.c
3.init_sequence[]
6.3.4 U-Boot與內核的關系
1.go命令的實現
2.bootm命令的實現
3.do_bootm_linux函數的實現