Download presentation
Presentation is loading. Please wait.
1
9. 부트로더 분석
2
9. 부트로더 분석 BLOB 실행 흐름 BLOB Memory map BLOB object 구조 주요 소스 코드 분석
3
9. 부트로더 분석 start.S main() run command Auto Boot Manual Boot boot_linux
SetClock Download() Flash() ...... Auto Boot Manual Boot GetCommand start.S 커널로 점프 명령어 모드 메모리 초기화 하드웨어 초기와 시리얼 타임머 커널 , 램디스크를 램에 복사 10 초간 가다린후 실행 키가 초안에 누렸을때 main() Blob는 어셈블러 코드인 start.S에서 시작한다. 이곳에서는 기본적인 하드웨어 초기화를 수행한 후 C 코드를 수행하기 위해 stack pointer를 설정하고 main.c의 c_main() 함수로 제어를 넘긴다. c_main()에서는 serial device와 timer를 초기화 한 후 커널과 램디스크 이미지를 램에 복사하면서 10초간 사용자의 입력을 기다린다.(현재 소스에는 1초로 되어있다.) 사용자가 아무런 입력도 하지 않으면 BootKernel() 함수를 수행하여 kernel로 제어를 넘기게 되고 사용자에게 입력(any key) 을 받으면 kernel로 부팅하지 않고 사용자에게 명령을 받을 준비를 하고 기다리게 된다.
4
9. 부트로더 분석 0xa7,fff,fff 0xa3,000,000 0xa0,700,000 0xa0,400,400
SDRAM (128M) FLASH ROM (32M) 0x01,fff,fff 0x00,480,000 0x00,280,000 0x00,080,000 0x00,040,000 0x00,000,000 0xa7,fff,fff | JFFS2 0xa3,000,000 ramdisk.gz 0xa0,700,000 BLOB main() 0xa0,400,400 BLOB down image 0xa0,300,000 zImage 0xa0,008,000 0x00,000,000 JFFS2 27.5 Mbytes ramdisk.gz 2 Mbytes zImage parameter 256 Kbytes BLOB 256 kbytes Blob 소스코드가 있는 디렉토리에서 include/blob/arch/pxa255_pro.h 에 보면 위의 메모리 영역에 대한 정의가 있다. Blob에서 ttftp “zImage” kernel 명령을 주면 위 그림에서 sdram 영역의 zImage 영역(0xa ) 부터 zImage를 저장한다. ttftp의 다른 인자들(blob, ramdisk) 도 동일하게 정해진 sdram 영역으로 host에 있는 파일을 복사한다. flash kernel 명령을 주면 sdram의 0xa 의 부터 512kbyte를 flash memory의 0x 에 복사한다. 다른 인자를 주어도 동일하게 정해진 영역의 sdram의 내용을 정해진 영역의 flash memory에 복사한다.
5
9. 부트로더 분석 BLOB는 2가지 stage로 구분 된다. Start stage Rest stage
start.S 로 시작하는 하드웨어의 기본적인 초기화를 담당하는 start stage Trampoline.S로 시작하는 실질 적인 기능을 담당하는 rest stage Start stage flash memory의 0x0번지 부터 위치하여 수행된다. cpu, memory 등 기본적인 하드웨어 초기화 모두 assembly 코드 Rest stage flash memory의 0x400번지 부터 위치하고 sdram으로 copy된 후 실행 된다. 실제적인 boot loader의 기능을 수행한다. 대부분 c 코드로 구성 Blob는 2가지 stage로 구별되는데 start stage와 rest stage이다. start stage에서는 cpu에 대한 설정(속도 및 내부 레지스터 초기화), 메모리 설정(메모리 관련 레지스터 초기화) 등 rest stage를 수행하기 위해 필요한 기본적인 하드웨어 초기화를 수행한다. 그리고 rest stage에서는 stack pointer를 설정하여 c 코드를 수행할 수 있도록 한 후 c 코드로 작성된 프로그램을 통해 커널을 적재하거나 ethernet, serial, flash 등의 기능을 사용할 수 있도록 해 준다.
6
_start(start.S)를 가장 처음 실행할 루틴으로 지정
9. 부트로더 분석 _start(start.S)를 가장 처음 실행할 루틴으로 지정 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x ; . = ALIGN(4); .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } .got : { *(.got) } .bss : { *(.bss) } } start-ld-script start-ld-script는 blob의 오브젝트들을 링크하여 최종 실행 이미지를 만들 때에 linker에서 참조하는 설정 파일이다. start stage의 프로그램을 만들때에 사용한다. OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") : elf32의 little endian 형식으로 코드를 생성한다. 인자가 3개인 경우는 링크할 때 옵션으로 –EB를 준 경우 –EL 을 준 경우 아무 것도 주지 않은 경우의 3 경우에 어떤 형식으로 코드를 생성할 것인가를 각각 정해주는 것이다. 위에서는 모든 경우에 elf32의 little endian 형식으로 코드를 생성하도록 하였다. OUTPUT_ARCH(arm) : 생성된 실행파일을 실행할 수 있는 architecture를 arm으로 설정 ENTRY(_start) : _start를 최초 실행 함수로 지정, _start는 start.S 파일에 있는 루틴이다. SECTIONS{} : 코드를 목적에 맞게 각 섹션에 분배, 0x (flash memory)부터 프로그램이 시작된다. 리눅스에서는 object code와 실행 파일 형식으로 ELF(Executable and Linkable Format)을 사용한다. ELF format에서는 source의 명령어 부분, data 부분을 각 section 별로 묶어서 관리하게 된다. . = 0x ; . 은 location counter이다 이것은 code가 object code내에서 위치할 곳의 주소를 나타내기 위해 사용한다. . = ALIGN(4); location counter를 4byte 단위로 정렬하는 것이다. 즉, 현재 . 에 0x 이 저장되어 있는데 이때에는 ALIGN(4) 가 동일하게 0x 을 return한다. 이 주소가 이미 4byte 단위로 정렬된 주소이기 때문이다. 반면 현재 . 이 0x 이라면 ALIGN(4)는 0x 를 return할 것이다. .text : { *(.text) } .text section에 source code의 명령어 부분을 저장. .rodata : { *(.rodata) } .rodata section에 Read only data를 저장 . .data : { *(.data) } .data section에 초기화 된 전역 변수를 저장. .got : { *(.got) } .got section에 position independent code를 위해 절대 주소를 dynamically mapping 해주기 위한 information을 저장. * PIC(Position Independent Code): code의 위치에 독립적으로 수행될 수 있는 code로 dynamic loading, linking을 위한 code에서 linker가 절대 주소를 dynamic 하게 mapping 해줄 수 있는 code를 말한다. .bss : { *(.bss) } 초기화 되지 않은 전역변수를 저장 *Linker script에 대한 자세한 내용은 아래 문서 참조 *Executable and Linkable Format 에 대한 자세한 내용은 아래 문서 참조
7
9. 부트로더 분석 rest-ld-script
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_trampoline) SECTIONS { . = (0xa ); . = ALIGN(4); .text : { __text_start = .; *(.text) __text_end = .; } … rest-ld-script는 rest stage의 프로그램을 만들때에 사용한다. OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") : elf32의 little endian 형식으로 코드를 생성한다. 인자가 3개인 경우는 링크할 때 옵션으로 –EB를 준 경우 –EL 을 준 경우 아무 것도 주지 않은 경우의 3 경우에 어떤 형식으로 코드를 생성할 것인가를 각각 정해주는 것이다. 위에서는 모든 경우에 elf32의 little endian 형식으로 코드를 생성하도록 하였다. OUTPUT_ARCH(arm) : 생성된 실행파일을 실행할 수 있는 architecture를 arm으로 설정 ENTRY(_trampoline) : _trampoline를 최초 실행 함수로 지정, _trampoline은 trampoline.S 파일에 있는 루틴이다. SECTIONS{} : 코드를 목적에 맞게 각 섹션에 분배, 0xa 부터 프로그램이 시작 된다. __text_start = .; __text_start를 global symbol로 정의하고 현재 locate pointer의 주소에 할당한다.
8
9. 부트로더 분석 Makefile … blob_start_elf32_OBJECTS = start.o testmem.o
blob_rest_elf32_OBJECTS = trampoline.o flashasm.o stack.o testmem2.o \ bootldrpart.o commands.o flash.o initcalls.o linux.o main.o memory.o \ param_block.o partition.o reboot.o uucodec.o scc.o net.o bootp.o tftp.o xmodem.o blob-start-elf32: $(blob_start_elf32_OBJECTS) $(blob_start_elf32_DEPENDENCIES) @rm -f blob-start-elf32 $(LINK) $(blob_start_elf32_LDFLAGS) $(blob_start_elf32_OBJECTS) \ $(blob_start_elf32_LDADD) $(LIBS) blob-rest-elf32: $(blob_rest_elf32_OBJECTS) $(blob_rest_elf32_DEPENDENCIES) @rm -f blob-rest-elf32 $(LINK) $(blob_rest_elf32_LDFLAGS) $(blob_rest_elf32_OBJECTS) \ $(blob_rest_elf32_LDADD) $(LIBS) start stage의 이미지인 blob-start-elf32와 rest stage의 이미지인 blob-rest-elf32가 각각 따로 만들어 지고 있음을 확인할 수 있다.
9
9. 부트로더 분석 Makefile (cont’) … blob-start : blob-start-elf32
$(OBJCOPY) $(OCFLAGS) $< blob-rest : blob-rest-elf32 blob: blob-start blob-rest rm -f dd if=blob-start bs=1k conv=sync dd if=blob-rest bs=1k seek=1 chmod +x 따로 만들어진 blob-start-elf32 와 blob-rest-elf32를 binary 파일로 변환한 후에 두 파일을 blob파일 하나로 합치는 것을 볼 수 있다. blob-start 파일의 처음에 들어가고 1kbyte 이후의 공간에 blob-rest가 들어가게 된다.
10
9. 부트로더 분석 trampoline.o flashasm.o stack.o testmem2.o bootldrpart.o
commands.o flash.o initcalls.o linux.o main.o memory.o param_block.o partition.o reboot.o uucodec.o scc.o net.o bootp.o tftp.o xmodem.o start.o testmem.o BLOB BLOB-start (1Kbyte) BLOB-rest (63Kbyte)
11
9. 부트로더 분석 start.S .text // Jump vector table as in table 3.1 in [1] g
.globl _start _start: b reset b undefined_instruction b software_interrupt b prefetch_abort b data_abort b not_used b irq b fiq .text : 이 코드가 최종 실행 파일의 text 섹션에 위치하도록 한다. .globl _start : _start를 외부에서 link가능하도록 선언 _start : blob에서 가장 먼저 수행될 루틴이다. 그러므로 ‘b reset’ instruction 이 가장 먼저 수행된다. 이는 reset 루틴으로 jump 하는 명령이다. 나머지 ‘b’ 명령들은 수행되지 않는 부분들이지만 형식상 포함시켜 놓은 것이다.
12
9. 부트로더 분석 start.S reset: // At the end of the reset sequence, MMU, Icache, Dcache, // and write buffer are all disabled. // Also IRQs and FIQs are disabled in the processor's CPSR // The operating mode is SVC (supervisory mode), and the // PC is vectored at 0x A branch in 0x // brings us directly here. // PXA255 Initialization bl init_xscale // GPIO Initialization bl init_gpio // SDRAM Initialization bl init_mem normal_boot: // check the first 1MB of BLOB_START in increments of 4k … reset 루틴에서는 xscale processor의 내부 레지스터와 gpio, sdram을 초기화 하는 루틴을 수행한다. init_xscale: operating mode 를 SVC(supervisory mode)로 바꾸고, MMU,instruction cache, data cache, write buffer 그리고 IRQ와 FIQ를 disable 시키고, instruction cache와 data cache를 flush 해 준다. init_gpio: gpio의 각 핀을 초기화 한다. 대부분 pin의 값이 high인 경우가 stable/disable 상태이므로 모든 핀을 set 해주고 law 상태이어야 하는 pin을 law로 바꾸어 준다.그리고 alternative function을 가진 pin을 적절한 state로 설정 해 준다. Init_mem: 각 memory에 접근할 때의 timing 등의 설정을 해준다. normal_boot: blob의 첫 1megabyte의 memory를 test하여 memory가 제대로 동작하면 계속해서 relocation: 루틴을 수행하고, 그렇지 않으면 badram 루틴으로 점프하여 led를 깜밖거리며 무한 loop에 빠진다.
13
9. 부트로더 분석 start.S relocate: adr r0, _start
// relocate the second stage loader add r2, r0, #(64 * 1024) // blob maximum size is 64kB add r0, r0, #0x400 // skip first 1024 bytes ldr r1, BLOB_START copy_loop: ldmia r0!, {r3-r10} stmia r1!, {r3-r10} cmp r0, r2 ble copy_loop // blob is copied to ram, so jump to it ldr r0, BLOB_START mov pc, r0 rest stage를 수행하기 위하 code를 sdram으로 복사하는 루틴이다. 또한 blob의 마지막 주소를 구하기 위해 64(kbyte)를 더해준다. 그리고 sdram에서 blob을 위치시킬 시작 주소부터 rest stage code를 복사한다. relocate: //먼저 rest stage의 code의 위치를 구하기 위해 blob의 시작 주소에 1024K byte를 더한 값을 저장한다. adr r0, _start // _start label이 붙은 곳의 주소, 즉 blob의 시작 주소 add r2, r0, #(64 * 1024) // blob maximum size is 64kB add r0, r0, #0x400 // skip first 1024 bytes, 즉 rest stage의 주소 ldr r1, BLOB_START // BLOB_START: rest stage가 저장될 sdram 시작 주소 copy_loop: ldmia r0!, {r3-r10} // r0에 저장된 주소부터 8 word를 r3부터 r10까지에 저장한다. // r0는 현재 rest stage의 시작주소를 가지고 있다. // instruction 수행 후 r0에는 8 word 다음 주소가 들어 있게 된다. stmia r1!, {r3-r10} // r3 부터 r10까지에 저장된 값을 r1에 저장된 주소 부터 8word 저장한다. // r1에는 현재 rest stage가 위치할 sdram의 시작 주소가 저장되어 있다. // instruction 수행 후 r1에는 8 word 다음 주소가 들어 있게 된다. cmp r0, r2 // r0와 r2를 비교한다. ble copy_loop // cmp에서 비교후에 r0가 r2보다 작으면 PSR 레지스터의 N flag를 set하고 같으면 Z flag를 set // ble 명령은 PSR의 N이나 Z bit가 set 되어 있으면 jump하는 명령이다. // 즉, 작거나 같으면 jump 한다. // 이 루틴을 통해 아직 rest_stage의 끝까지 복사가 되지 않았으면 loop를 돌면서 나머지를 복사하게 된다. // blob is copied to ram, so jump to it ldr r0, BLOB_START // rest stage가 저장되어 있는 sdram의 시작 주소로 jump mov pc, r0
14
9. 부트로더 분석 trampoline.S .text .globl _trampoline _trampoline:
/* clear the BSS section */ ldr r1, bss_start ldr r0, bss_end sub r0, r0, r1 /* r1 = start address */ /* r0 = #number of bytes */ mov r2, #0 rest stage의 첫 코드이다. /* clear the BSS section, 초기화 되지 않은 전역변수를 0으로 초기화 해준다. */ ldr r1, bss_start // bss 시작 주소 ldr r0, bss_end // bss 마지막 주소 sub r0, r0, r1 // r0에 bss의 크기 저장 /* r1 = start address, */ /* r0 = #number of bytes */ mov r2, #0 // r2에 0 저장
15
9. 부트로더 분석 trampoline.S clear_bss: stmia r1!, {r2} subs r0, r0, #4
bne clear_bss /* setup the stack pointer */ ldr r0, stack_end sub sp, r0, #4 /* jump to C code */ bl main /* if main ever returns we just call it again */ b _trampoline bss_start: .word __bss_start bss_end: .word __bss_end stack_end: .word __stack_end clear_bss: // r1에 저장된 주소부터 bss 마지막 주소까지 clear stmia r1!, {r2} subs r0, r0, #4 bne clear_bss /* setup the stack pointer, stack pointer를 sdram의 stack 영역의 마지막 주소 – 4로 설정*/ ldr r0, stack_end sub sp, r0, #4 /* jump to C code, main() 함수로 jump*/ bl main /* if main ever returns we just call it again */ b _trampoline
16
9. 부트로더 분석 main()/main.c int main(void){ … init_subsystems();
clientIP = inet_addr(CLIENT_IPADDR); hostIP = inet_addr(HOST_IPADDR); TimerInit2(); blob_status.paramType = fromFlash; // or download blob_status.kernelType = fromFlash; blob_status.ramdiskType = fromFlash; blob_status.rootType = fromFlash; blob_status.usrType = fromFlash; blob_status.downloadSpeed = baud_115200; blob_status.terminalSpeed = baud_115200; blob_status.load_ramdisk = 1; blob_status.cmdline[0] = '\0'; blob_status.boot_delay = 1; init_subsystems(); blob에 기능이 추가될 때마다 기 기능을 위한 초기화 code를 추가하기 위해 main( )함수를 수정해야 한다면 module화에 문제가 발생한다. 그렇기 때문에 blob에서는 initlist, commandlist, ptaglist section을 두고 __initlist( ), __commandlist( ), __ptagtable( ) 매크로를 사용해 이 section에 code를 저장할 수 있는 기능을 두었다. 때문에 추가되는 code에서 초기화때 수행되어야 하는 함수가 있다면, main( )을 고쳐서 해당함수를 호출하는 대신 __initlist( ) 매크로를 사용해 initlist section에 code를 저장하도록 하고 main( )에서는 initlist section에 저장되어 있는 code들을 수행하는 방식으로 처리 할 수 있다. Command의 경우도 동일한 방식을 사용하며 이 후에 좀 더 자세히 다룬다. clientIP = inet_addr(CLIENT_IPADDR); hostIP = inet_addr(HOST_IPADDR); clientIP와 hostIP(main.h 에서 정의)를 가져온다. inet_addr( ) 함수는 “ ” 과 같은 string을 integer로 바꾸어 주는 함수이다. TimerInit2(); Timer를 사용하기위해 초기화 한다. blob_status.paramType = fromFlash; // or download blob_status.kernelType = fromFlash; blob_status.ramdiskType = fromFlash; blob_status.rootType = fromFlash; blob_status.usrType = fromFlash; 각 image를 flash에서 가져올 것인지 download할 것인지를 결정한다. fromFlash는 enum type 변수의 element로 fromFlash와 download 두개 가 있다. blob_status.downloadSpeed = baud_115200; blob_status.terminalSpeed = baud_115200; serial unit의 속도를 설정한다. blob_status.load_ramdisk = 1; Ramdisk를 load하는 것으로 설정 blob_status.cmdline[0] = '\0'; 사용자가 입력한 명령을 저장할 변수를 초기화 blob_status.boot_delay = 1; Linux로 부팅하기 전에 사용자가 key를 입력하여 blob command mode로 들어갈 수 있도록 기다리는 시간.
17
9. 부트로더 분석 main()/main.c /* parse the core tag, for critical things like terminal speed */ #ifdef PARAM_START parse_ptag((void *) PARAM_START, &conf); #endif … /* get the amount of memory */ get_memory_map(); parse_ptags((void *) PARAM_START, &conf); /* Load kernel and ramdisk from flash to RAM */ do_reload("blob"); //BLOB_RAM_BASE에 blob를 적재 do_reload("kernel"); //KERNEL_RAM_BASE에 kernel 적재 do_reload("ramdisk"); //RAMDISK_RAM_BASE에 ramdisk 적재 parse_ptag((void *) PARAM_START, &conf); Flash memory map을 보면 parameter block이 있다. 이곳에는 blob의 설정을 저장해 둘 수 있는데 이곳에 있는 설정을 사용하기 위해 호출하는 함수이다. PARAM_START는 parameter의 시작 주소이다. parameter block는 blob 에서 제공하는 util인 mkparamblock 를 통해 만들 수 있다. Mkparamblock는 하나의 설정파일을 입력으로 받는데 그 예제는 다음과 같다. # Sample paramater block configuration file ramdisk no # default is yes bootdelay 1 # default is 10 cmdline console=ttySA0,9600 console=tty1 root=/dev/mtdblock2 init=/linuxrc baud 9600, 115k2 # these are the defaults hwaddr eth0 00:11:22:33:44:55 보면 쉽게 알 수 있듯이 ramdisk를 사용하는지 boot delay를 얼마로 해줄건지 등에 대한 정보가 들어 있다. mkparamblock에 이 파일을 입력으로 주어 실행하여 생성된 image를 flash memory 의 paramter block에 fusing해두면 blob에서 이 정보를 가져다 사용할 수 있는데 그 함수가 parse_ptag( )와 parse_ptags( )이다. 이 함수는 ptaglist section (object code의 section 중 하나이다. __ptaglist( ) 매크로를 통해 이곳에 코드를 저장할 수 있다.) 에 저장되어있는 가장 첫 함수를 실행한다. 가장 첫 함수는 uart의 baud rate에 대한 값을 parameter block에서 가져오는 함수이다. 여기서 PARAM_START는 flash memory에서 parameter block의 시작 주소이다. 그리고 conf는 확장을 위해 만들어 둔 것으로 추정되는 데 mkparamblock 프로그램의 source를 분석해보면 특별히 사용하는 예가 없이 모두 0 값으로 되어있다. 현재 개발 버전의 blob를 사용하므로 아직 conf에 대한 부분은 완료가 되지 않은 것으로 보인다. conf는 0으로 넘겨주면 문제없이 수행된다. parse_ptags((void *) PARAM_START, &conf); parameter block에 해당하는 설정 값이 있는 경우에 한해서 ptaglist secton에 저장된 나머지 함수들을 수행하여 parameter block에서 값을 가져오는 것이다. 나머지 함수들은 우 설정 파일에서 볼 수 있듯 ramdisk로 부팅할 것인지의 여부, command line argument 등의 설정값을 가져온다. 현재 완전한 검증이 끝나지 않은 기능이고 또한 중요한 부분이 아니므로 code의 자세한 설명은 생략하였다. get_memory_map(); memory의 사용 가능한 양을 구하는 함수이다. kernel과 do_reload( ); blob/kernel/ramdisk 의 이미지를 flash memory에서 sdram으로 복사 한다.
18
9. 부트로더 분석 main()/main.c EthInit();
/* wait 10 seconds before starting autoboot */ SerialOutputString("Autoboot in progress, press any key to stop "); for(i = 0; i < blob_status.boot_delay; i++) { serial_write('.'); retval = SerialInputBlock(commandline, 1, 1); if(retval > 0) break; } if(retval == 0) { commandline[0] = '\0'; parse_command("boot"); EthInit(); ethernet을 사용하기위해 CS8900A를 초기화 해준다. CS8900A의 초기화에 대해서는 network device driver에서 다룬다. Kernel과 ramdisk를 sdram으로 올린후에는 사용자의 입력을 기다려 boot_delay의 값(초)동안 기다린 후 입력이 없으면 kernel로 부팅한다. SerialInputBlock(commandline, 1, 1); 첫번째 인자는 사용자의 명령을 저장할 buffer이고 두번째 인자는 몇 byte를 저장할 것인지 size를 넘겨주는 것이고 마지막 인자는 몇초동안 기다릴 것인지를 알려주는 것이다. 즉, 사용자의 입력을 1byte만 command에 저장하는 것이고 사용자가 입력하거나 또는 1초가 지나면 return하는 것이다. retval은 읽은 byte 수를 return한다. 여기서는 사용자가 입력한 경우 1이 return되고 1초동안 입력하지 않은 경우 0이 return된다. parse_command("boot"); 사용자가 아무런 key도 입력하지 않은 경우 이 함수를 호출하여 linux로 부팅하게 된다. 이 함수는 object code의 commandlist section에 저장되어 있는 함수중 boot라는 keywork를 통해 해당 함수를 찾아 실행하는 기능을 한다. commandlist section에는 __commandlist( ) 매크로를 통해 함수를 저장할 수 있는데, 중요한 부분이므로 뒷 부분에서 따로 설명하도록 하겠다.
19
9. 부트로더 분석 main()/main.c … /* the command loop. endless, of course */
for(;;) { DisplayPrompt("boot> "); /* wait 10 minutes for a command */ numRead = GetCommand(commandline, MAX_COMMANDLINE_LENGTH, 600); if(numRead > 0) { if((retval = parse_command(commandline)) < 0 ) printerror(retval, NULL); } return 0; } /* main */ 사용자가 key를 입력하여 blob command mode로 들어간 경우에 해당하는 code이다. DisplayPrompt("boot> "); “boot>” 라는 prompt를 출력한다. GetCommand(commandline, MAX_COMMANDLINE_LENGTH, 600); 사용자에게 명령을 입력 받는다. 첫번째 인자 command는 사용자가 입력한 명령을 저장할 buffer이고, MAX_COMMANDLINE은 명령어의 최대 길이이다. 600은 사용자가 명령을 입력할 때까지 기다릴 시간(초) 이다. 사용자가 입력한 character 수를 return한다. parse_command(commandline)) 사용자가 명령을 입력한 경우 해당 명령을 수행한다.
20
9. 부트로더 분석 init_subsystems()/init.c void init_subsystems(void) {
int i; /* call all subsystem init functions */ for(i = INIT_LEVEL_MIN; i <= INIT_LEVEL_MAX; i++) call_funcs( (initlist_t *)&__initlist_start, (initlist_t *)&__initlist_end, INIT_MAGIC, i);} /* init.h */ … #define __init __attribute__((unused, __section__(".initlist"))) #define __initlist(fn, lvl) \ static initlist_t __init_##fn __init = { \ magic: INIT_MAGIC, \ callback: fn, \ level: lvl } /* rest-ld-script */ … . = ALIGN(4); .initlist : { __initlist_start = .; *(.initlist) __initlist_end = .; } main( ) 함수의 시작 부분에서 호출한 init_subsystems( ) 함수이다. 이 함수는 각 level에 등록되어있는 초기화 함술를 호출하게 된다. Level은 초기화가 먼저 진행되어야 하는 함수에 우선순위를 주기위한 것이다. INIT_LEVEL_MIN 이 가장 우선순위가 높은 것이고 ( init.h에 0 으로 정의되어 있다.) INIT_LEVEL_MAX가 우선순위가 가장 낮은 것이다.( init.h에 99로 정의 되어 있다.) call_funcs( (initlist_t *)&__initlist_start, (initlist_t *)&__initlist_end, INIT_MAGIC, i); blob object code의 initlist section에 있는 함수들을 호출해주는 함수이다. __initlist_start는 initlist section의 시작 위치이고 __initlist_end는 마지막 위치이다. 위 슬라이드 왼쪽 아래 text box에 보면 rest-ld-script에서 initlist section이 있음을 확인할 수 있다. 그리고 __initlist_start변수가 initlist의 시작 주소에 __initlist_end가 initlist의 마지막 주소에 할당되어 있음을 알 수 있다. INIT_MAGIC은 initlist의 함수가 맞는지 확인하기 위한 매크로이다. i 는 level값으로 앞서말했듯이 각 초기화 함수들의 우선순위를 구별하기 위한것이다. initlist section에는 __initlist( )매크로를 사용하여 등록할 수 있다. 위 슬라이드 오른쪽 아래 text box를 보면 __initlist( ) 매크로의 정의를 볼 수 있다. 다음과 같은 코드가 있다고 생각해 보자 //INIT_LEVEL_DRIVER_SELECTION은 init.h에 0으로 정의된 매크로로 가장 우선순위가 높은 것이다. __initlist( example_func, INIT_LEVEL_DRIVER_SELECTION ); 이 매크로는 cpp에 의해서 다음과 같이 확장될 것이다. Static initlist_t __init_example_func __attribute__((unused,__section__(“.initlist”))) = { magic: INIT_MAGIC, callback: example_func, level: LEVEL_DRIVER_SELECTION } __attribute__ 키워드는 gcc 확장 기능으로 위 코드의 내용은 object code의 .initlist section에 이 structure 변수를 저장하라는 것이고, 이 변수가 사용되지 않을 수 도 있다는 것을 컴파일러에게 알리는 것이다.
21
9. 부트로더 분석 call_funcs()/init.c
static void call_funcs(initlist_t *start, initlist_t *end, u32 magic, int level) { initlist_t *item; for(item = start; item != end; item++) { if(item->magic != magic) { printerror(EMAGIC, NULL); … return; } if(item->level == level) { /* call function */ item->callback(); /* init.h */ … typedef void(*initfunc_t)(void); typedef struct { u32 magic; initfunc_t callback; int level; } initlist_t; 앞 슬라이드에서 이미 모두 설명했던 내용이다. Text box에 initlist_t structure의 형태와 __initlist( ) 매크로를 사용하는 실제 코드를 발췌하여 나타내었다. /* pxa255_pro.c */ … static void pxa255_pro_init_hardware(void) { /* select serial driver */ serial_driver = &pxa255_pro_serial_driver; } __initlist(pxa255_pro_init_hardware, INIT_LEVEL_DRIVER_SELECTION);
22
9. 부트로더 분석 command.h www.huins.com /* command.h */ …
typedef int(*commandfunc_t)(int, char *[]); typedef struct commandlist { u32 magic; char *name; char *help; commandfunc_t callback; struct commandlist *next; } commandlist_t; #define __command __attribute__((unused, __section__(".commandlist"))) #define __commandlist(fn, nm, hlp) \ static commandlist_t __command_##fn __command = { \ magic: COMMAND_MAGIC, \ name: nm, \ help: hlp, \ callback: fn } command 에 대한 처리도 앞의 initlist와 동일한 방식을 사용한다. commandlist_t 스트럭쳐를 보면 initlist_t 에서와 동일한 용도로 사용되는 magic 변수가 있고 명령어 이름을 나타내는 name 그리고 사용자가 “help 명령어” 를 입력했을 때 출력해 주는 string인 help, 그리고 명령을 수행할 실제 함수에 대한 포인터를 가지고 있다. 또 .commandlist section에 저장된 commandlist_t 스트럭쳐들을 linked list로 만들어 주기위한 next 포인터 변수도 있다.
23
9. 부트로더 분석 init_command()/commands.c commandlist_t *commands;
/* rest-ld-script */ … . = ALIGN(4); .commandlist : { __commandlist_start = .; *(.commandlist) __commandlist_end = .; } commandlist_t *commands; static void init_commands(void) { commandlist_t *lastcommand; commandlist_t *cmd, *next_cmd; commands = (commandlist_t *) &__commandlist_start; lastcommand = (commandlist_t *) &__commandlist_end; cmd = next_cmd = commands; next_cmd++; while(next_cmd < lastcommand) { cmd->next = next_cmd; cmd++; } __initlist(init_commands, INIT_LEVEL_OTHER_STUFF); /* main.c */ … static int Flash_erase(int argc, char *argv[]) { … } static char erasehelp[] = "erase <start> <len>“ \ "flsah erase \n“ \ "erase <start> <len>, ex) erase \n"; __commandlist(Flash_erase, "erase", erasehelp); init_commands(void) .commandlist에 저장되어있는 commandlist_t 스트럭쳐들을 연결하여 linked list를 만들어 주는 함수이다. 맨 아래줄에서 __initlist( ) 매크로를 사용해 이 함수를 .initlist section에 저장하도록 하고 있다. 이를 통해 init_subsystems( ) 함수 호출에 의해 이 함수도 실행되게 되고 이때 .commandlist 에 저장된 commandlist_t 스트럭쳐들의 linked list가 생성된다.
24
9. 부트로더 분석 parse_command()/commands.c int parse_command(char *cmdline)
{ commandlist_t *cmd; int argc, num_commands, len; char *argv[MAX_ARGS]; parse_args(cmdline, &argc, argv); /* only whitespace */ if(argc == 0) return 0; int parse_command(char *cmdline) 사용자가 입력한 string을 분석해 해당 명령을 수행하는 함수이다. parse_args(cmdline, &argc, argv); cmdline을 분석해 arguement 들을 저장하는 argv와 argument들의 수를 저장하는 argc 에 값을 기록해준다. argc가 0이면 즉 사용자가 whitespace외에 아무런 입력도 하지 않았다면 아무일도 하지 않고 return한다.
25
9. 부트로더 분석 parse_command()/commands.c
num_commands = get_num_command_matches(argv[0]); /* error */ if(num_commands < 0) return num_commands; /* no command matches */ if(num_commands == 0) return -ECOMMAND; /* ambiguous command */ if(num_commands > 1) return -EAMBIGCMD; len = strlen(argv[0]); get_num_command_matches(argv[0]); argv[0] 는 사용자가 입력한 string에서 white space로 구별된 가장 처음 string이다. 즉 명령어 이름이다. 이 함수는 argv[0] 와 동일한 내용의 name filed를 갖는 commandlist_t 스트럭쳐가 .commandlist section에 존재하는 가를 검사해서 같은 name field를 갖는 스트럭쳐들의 수를 return하는 함수이다. 에러가 발생한 경우에는 -1을 return한다. 결과 값이 0보다 작으면 error, 0과 같으면 일치하는 command가 없는 것이며 1보다 큰 경우는 일치하는 command가 둘 이상 발견된 것으로 에러 처리한다.
26
9. 부트로더 분석 parse_command()/commands.c /* single command, go for it */
for(cmd = commands; cmd != NULL; cmd = cmd->next) { if(cmd->magic != COMMAND_MAGIC) { return -EMAGIC; } if(strncmp(cmd->name, argv[0], len) == 0) { /* call function */ return cmd->callback(argc, argv); return -ECOMMAND; argv[0] 와 동일한 내용의 name field를 갖는 commandline_t 스트럭쳐가 단 하나만 발경되었다면 해당 스트럭쳐를 검사하여 적합하다면 해당 스트럭쳐의 callback 함수 포인터에 연결된 함수를 호출하여 명령을 수행한다.
27
9. 부트로더 분석 boot_kernel()/linux.c
static int boot_linux(int argc, char *argv[]) { void (*theKernel)(int zero, int arch) = (void (*)(int, int))KERNEL_RAM_BASE; … /* start kernel */ theKernel(0, ARCH_NUMBER); SerialOutputString("Hey, the kernel returned! This should not happen.\n"); return 0; } static char boothelp[] = "boot [kernel options]\n" "Boot Linux with optional kernel options\n"; __commandlist(boot_linux, "boot", boothelp); blob의 흐름중 가장 마지막에 위치하는 code로 linux로 실행제어를 옮겨 주는 것이다. theKernel 함수 포인터를 KERNEL_RAM_BASE를 가리키도록 한다. KERNEL_RAM_BASE는 kernel image가 loading되어있는 sdram의 주소이다. 마지막으로 theKernel 을 호출하여 KERNEL_RAM_BASE로 실행제어가 넘어가게 된다. theKernel(0, ARCH_NUMBER); 첫 인자는 무조건 0이고(이 인자는 r0에 저장되는데 kernel의 시작 코드인 head.S에서 단순히 명령어 cache를 비우기 위해 mov r0, r0 instruction을 8번 반복하여 수행하는 데에 사용된다.) 두번째 인자는 architecture number이다. 이는 linux.h( “blob 소스코드 디렉토리”/include/blob/linux.h) 에서 각 architecture 별로 다르게 정의 되어있다. 마지막 줄에 __commandlist 매크로를 통해서 명령어로 등록하는 것을 볼 수 있다.
28
9. 부트로더 분석 Example (command add)
Static int example_command(int argc, char *argv[]) { int i; for( I = 0; I < argc; i++ ){ SerialOutputString(“argv[“); SerialOutputDec(i); SerialOutputString(“] = \””); SerialOutputString(“\”\n”); } return 0; static char examplehelp[] = “example command\n”; __commandlist(example_command, “example”, examplehelp); commandlist의 기능에 대해서는 이미 앞에 설명하였다. 이 와같은 방식으로 새로운 명령어를 추가할 수 있을 것이다.
Similar presentations