임베디드 레시피

Egloos | Log-in





ARM Assembly를 파헤쳐 보자 ADS vs GNU

무엇이 되었든, Assembly를 다룬다는 건 Software Engineer로서는 상당히 강력한 무기를 가졌다고 할 수 있습니다. Assembly에만 익숙하다면 Software Debugging에도 유리하고, 심지어 Reverse Engineering도 할 수 있습니다. 그러면 가장 간단한 Assembly program 예제를 하나 들어서 Assembly는 어떻게 구성되는지 알아보도록 하겠습니다. Assembly Structure & Syntax라고나 할까요.
 
아래 예제는 간단한 Assembly 예제 인데요. 언제나 그랬듯이 Hello World 예제 입니다. 그다지 Hello World만 찍으면 심심하니까, 몇 가지를 조금 더 첨부해서 풍성한 이해를 도와야 겠지요. ㅋ . 직접 Assembly로 Hello World를 출력하는 예제를 만들어 보겠습니다.
 
 
Helloworld.s -----------------------------------------------------------------------
 
         CODE32
 
         AREA Helloworld CODE, READONLY
 
         ENTRY                      
 
BEGIN   ; LABEL
            ADR r0, THUMB+1
            BX r0            
 
         CODE16
 
THUMB   ; LABEL
            ADR r1, TEXT
 
LOOP    ; LABEL
            LDRB r0, [r1], #1    
            CMP r0, #0             
            BLNE print_ch       
            BNE LOOP       
            B EXIT       
 
TEXT = "Hello World ", 0          ; TEXT → LABEL
           
        END
 
----------------------------------------------------------------------------------
 
어때요? 간단해 보이나요? 그 속을 해부해 보져 머.


 

 
자 보니까 어떠세요? 뭐 여기저기 기웃거려 보면 Directive니 뭐 어쩌구 해서 어렵게 설명해 놓았습니다만, 크게는 Directive라고 불리는 것들과 Label, Instruction, 그리고 Comment로 나뉘죠. 간단하게 공식적인 syntax를 알아보시죠. (Directive 위주로)
 
 
AREA
 
일단 Code의 모음을 하나의 AREA로 묶을 수 있습니다. 이런 AREA는 어떤 assembly의 block의 속성을 정해 줄 수 있습니다. 그러니까 이게 CODE인지, DATA인지 또는 READONLY인지 READWRITE 인지 등을 정해서 묶음을 만들 수 있습니다. 이런 AREA는 이름을 가지며, 이 이름을 근거로 Scatter Loading등에서 메모리에서의 위치를 지정할 수도 있습니다. 조금 더 유식하게 말한다면 ELF file format에서 Section이라고 보시면 되고, linker가 다루는 단위 중 최소 단위라고 보시면 됩니다. 나눌 수 없는 거죠. Linker는 이 AREA단위로 주소를 할당한다고 보시면 됩니다. 결국엔 elf format의 section과 같다고 보시면 됩니다. AREA를 만들면서 줄 수 있는 속성은,
 
ALIGN : alignment. default 4 byte
CODE : contain machine instruction.
DATA : contain data
COMDEF : common section definition
COMMON : common data section
NOINIT : uninitialized data section or initialized to   zero
READONLY : Read only section
READWRITE : Read/ Write section
 
이에요.
 
ENTRY
 
AREA에 ENTRY가 따라 붙으면 AREA에서 수행되어야 할 첫 번째 위치를 가리킵니다. 보통은 난생처음 진입하는 곳에 이런 것이 따라 붙는데, 제일 먼저 pc가 어디를 가리켜야 하는지를 모르니까 그걸 알려준다고 생각하면 됩니다.
 
CODE32/ CODE16
 
Assembler에게 CODE32를 만나면 32bit ARM code로 CODE16을 만나면 16bit Thumb code로 되어 있다고 알려주는 지시어 에요. 그러니까, 직접 C 컴파일러로 파일 하나를 컴파일 하면 16bit Thumb code나 32bit ARM code로 밖에 못 만드는데, Assembly code로 직접 짜면 파일 하나 안에도 16 bit Thumb code와 32 bit ARM code를 혼재 시킬 수 있다는 의미 이지요.
 
LABELS : BEGIN/ THUMB/ LOOP/ TEXT
 
BEGIN/ THUMB/ LOOP/ TEXT는 label로서 코딩 하는 사람 맘대로 이름 붙인 이름표 입니다. 이름표를 붙여놓으면 그 이름표 자체가 Symbol의 의미가 되어 이름표 = Label이 있는 곳의 주소를 의미하게 됩니다. 그래야 Data를 가리키던, 어디론가 branch (Jump)를 하든 할 것 아니겠습니꺄? ㅋ
 
END
Assembly로 짜여진 file의 끝을 의미합니다. File이 끝났다고 Assembler에게 알려주는 거지요. 후후.
 
뭐 대충의 구조 얘기를 끝냈으니, 어떤 Assembly 내용인지 다시 한번 자세히 한번 보시겠사옵니까? Assembly에서 comment는 ; 으로 달고요 그 응용으로 예시의 Assembly에 그 설명들을 달아 보았습니다 깜찍할까 몰라.
 
 
Helloworld.s -----------------------------------------------------------------------
 
CODE32                                     ; ARM mode로 짰삼.
 
AREA Helloworld CODE, READONLY  ; 이 코드 block의 이름과 속성, 이름은 HelloWorld
 
ENTRY                       ; Instruction이 제일 처음 실행할 곳.
 
BEGIN 
            ADR r0, THUMB+1  ; r0에 THUMB label의 주소를 넣음.
            BX r0                  ; r0값으로 점프
 
CODE16                           ; 여기서부터는 THUMB mode로 컴파일 해줘.
 
THUMB
            ADR r1, TEXT  ; r1 ← "Hello World"의 주소
 
LOOP   LDRB r0, [r1], #1          ; r0에 r1가 가르키는 주소에 들어 있는 내용을
                                             1byte 만큼 Load한 후 r1의 값을 1 증가해야 해.
            CMP r0, #0              ; 읽어들인 값이 0인지 비교하여 끝인지 확인하자.
            BLNE print_ch           ; printf_ch로 갔다 오자.
            BNE LOOP                ; 만약 0이 아니면 다시 하나 읽어들이기 위해 LOOP로 닥치고 돌아가
            B EXIT                    ; 0이면 EXIT로 jump
 
TEXT = "Hello World ", 0          ; Data
           
END                                     ; Assembly file의 끝이여.
 
 
어때요. 대충 감이 오시죠. 뭐 간단합니다. 크흑 역사적인 Hello world가 탄생하는 순간입니다. 처음부터 보시면 ENTRY가 있으니까 BEGIN부터 시작하고요, THUMB으로 branch해서 Hello world의 주소를 가져다가 1byte씩 장난쳐서 출력하는 프로그램 입니다. 그런데, 여기서 한가지 THUMB으로 branch할 때 THUMB+1을 해주는 이유는 뭘까요? THUMB mode와 ARM mode는 어차피 2byte와 4byte이기 때문에 시작 주소가 짝수일 수 밖에 없으니까 2byte짜리 Thumb과 4byte ARM의 구분을 branch 할 때 branch할 target주소의 끝이 홀수면 Thumb, 짝수면 ARM으로 구분합니다. 실제로는 Thumb mode로 branch 하고 난 후 홀수지만 마지막의 1을 뺀 짝수 값에서 실행을 하고요, 그러니까 0x1001로 branch를 한다면, 0x1000으로 Thumb mode로 실행한다는 의미고요, CPSR의 ARM/Thumb bit만 Thumb으로 setting해 준답니다. 자세한 얘기는 ARM과 Thumb mode의 Veneer에서 하고요, 그냥 그렇다고 넘어가시죠.
 
자자, 그러면 실제로 Hello world를 C file로 짠 후, 그걸 Assembler로 만들면 이거랑 비슷하게 나올까요? 절~대~로 그렇지는 않습니다~ 왜냐하면~ 위의 예제는 순전히 설명을 위해서 가라로 만든 코드니까요. 아래의 Hello world C code를 한번 Assembly로 만들어 봅시다. 실제로는 뭐가 다른가. 일단은 이런 코드들을 보면서 공부를 하면 더~욱 금방 Assembly에 익숙해 질 수 있습니다. 우선은 Hello world의 C code는 다음과 같습니다.
 
 
helloworld.c -------------------------------------------------------------------------------
 
void hello ()
{
 char *data;
 const char text[]="Hello world";
 
 data = (char *)text;
 printf (" %s ", data);
 return; 
}
 
간단하지유? 누가 봐도 알만한 Hello World 프로그램 입니다. 이 녀석을 ARM mode로 컴파일 해볼까요? 우리 이미 배운 Assembly까지만 만드는 -S option으로 한번 만들어 보겠습니다. 
 
armcc -o helloworldarm.s -S helloworld.c
 
 
; generated by ARM C Compiler, ADS1.2 [Build 805]
; commandline [-O2 -S -IC:\apps\ADS12\INCLUDE]
        CODE32
        AREA ||.text||, CODE, READONLY
hello PROC
|L1.0|
        STMFD    sp!,{r1-r3,lr}
        MOV      r0,sp
        MOV      r2,#0xc
        LDR      r1,|L1.36|
        BL       __rt_memcpy
        MOV      r1,sp
        ADR      r0,|L1.40|
        BL       _printf
        LDMFD    sp!,{r1-r3,pc}
|L1.36|
        DCD      ||.constdata$1||
|L1.40|
        DCB      " %s "
        DCB      "\0\0\0\0"
        ENDP
 
        AREA ||.constdata||, DATA, READONLY, ALIGN=0
||.constdata$1||
        DCB      0x48,0x65,0x6c,0x6c
        DCB      0x6f,0x20,0x77,0x6f
        DCB      0x72,0x6c,0x64,0x00
 
END
 
오 비슷한 듯 하면서 비슷하지 않은 code가 만들어 졌네요. 일단은~ 코멘트를 통해서 하나 하나 살펴 보시도록 하시시십다.
 
        CODE32                                       ; 자, 32bit arm mode라는 얘기구요.
        AREA ||.text||, CODE, READONLY     ; linker의 최소단위인데 이름은 ||.text.||이고
                                                                 CODE속성에 READONLY임다
hello PROC                                            ; 함수의 시작인데 이름은 hello 에요.
|L1.0|                                                  ; 레이블이고요. 누군가가 이 주소를 참조하나 보죠?
        STMFD    sp!,{r1-r3,lr}                    ; 일단 stack에 r1~r3하고 lr을 backup하고 시작합시다.
        MOV      r0,sp                                ; 요기서부터는 printf를 사용하기 위한 setting들이에요.
        MOV      r2,#0xc                            ; 
        LDR      r1,|L1.36|                          ; |L1.36| 레이블에 있는 ||.constdata$1||을 r1에 load하고요. 
                                                           ; 요게 Hello World
                                                          ; 저기~ 뒤에 AREA가 하나 더 있네요? READONLY data를 위한.
        BL       __rt_memcpy                      ; 일단은 text를 data에 복사하기 위한 함수로 갔다 옴다
        MOV      r1,sp                                
        ADR      r0,|L1.40|                         ; r0에는 |L1.40|레이블의 주소를 넣습니다.
        BL       _printf                                ; printf로 갔다 오네요.
        LDMFD    sp!,{r1-r3,pc}                   ; 그리고 위로 돌아가기 위해서 stack에 넣었던 값들을
                                                              복원합니다.
                                                           ; lr을 pc에 집어 넣으면 원래 호출되었던 장소로 돌아갈 수
                                                             있다는 의미 인데요,
                                                           ; 이건 뒤에 함수의 호출과정을 자세히 다루니까, 
                                                            그때 또 자세히 봐요.
|L1.36|
        DCD      ||.constdata$1||
|L1.40|
        DCB      " %s "
        DCB      "\0\0\0\0"
        ENDP                                           ; hello PROC의 끝을 알립니다.
 
        AREA ||.constdata||, DATA, READONLY, ALIGN=0
||.constdata$1||
        DCB      0x48,0x65,0x6c,0x6c
        DCB      0x6f,0x20,0x77,0x6f
        DCB      0x72,0x6c,0x64,0x00
 
END
 
몇가지 syntax를 본다면..
 
함수이름 PROC
함수가 시작된다는 의미이고, 함수의 이름이 앞에 주어집니다.
 
ENDP
함수가 끝난다는 의미에요.
 
직접 짠 거는 printf를 안 써서 좀 어렵게 짰지만, 막상 compile해서 나온 넘이랑 Idea자체는 비스므리 합니다 - 라고 해 놓고 별로 그렇지 않은데 하고 갸우뚱 하는 분들도 있을 거라는 생각이 듭니다-. 비스므리 안 하더라도 Assembly에 더 익숙해 진다고 보면 마냥 valueless하지만은 않을 걸요. 우선 차이점은 standard library인 printf를 사용하고, hello()함수는 ENTRY없이 누군가가 불러 쓸 거라는 가정으로 compile하니까 stack관련한 처리도 존재하고, hello PROC하고 ENDP도 사용하게 되네요.
 
어쨌거나, 이런 식으로 ARM assembly는 구성되니까, 그 구성을 잘 이해 하시면 Reverse Engineering도 금방이라니까요. 하악하악.
 
또 한가지, Assembly를 설명하면서 * (레지스터이름) 등의 pointer 형식의 설명을 쓸 때가 더러 있는데, 이건 레지스터이름의 값은 주소이고 이 주소가 가리키는 곳의 값을 의미합니다.
이게 편할 때도 있어 가끔 사용하니까 주의해 주세요.
 
지금까지는 ADS를 기준으로 Assembly를 설명 했습니다만, 요즘은 Linux나 OpenOS가 주목 받고 있는 추세라 잠시 GNU Assembly를 짚고 넘어가야겠습니다. 실제 Assembly는 Register가지고 장난치는 거니까 같다고 보시면 되고요. 다른 점은 Directive가 다르다는 점이에요.
 
뭐, 뭐 GNU 얘기는 잠시 집어 치우고, ADS에서 잘 쓰는 몇가지 Directive들을 늘어놔 볼게요. 
 
ALIGN
: 이 Directive를 만나면 이 이후부터는 ALIGN의 bit 수 만큼씩 compiler가 정렬을 해주지요
 
EQU
: C에서의 #define하고 똑같사옵니다.
  #define SDRAM_BASE 0x20000000
  SDRAM_BASE    EQU   0x20000000
  똑같은 표현이에요.
 
MACRO
: 내 사랑 Macro네요. 역시나 Assembler에서도 Macro를 만들 수 있겠습니다. (내부에서는 $라는걸로 처리하는데요.)
  MACRO로 시작해서 MEND로 끝나면 되는데요, 마치 C의 함수처럼 인자를 받을 수도 있습니다.
  MMU의 Page able base를 set하는 Assembly Macro를 만들어 본다면,
 
   MACRO
   mmu_page_table $address
   ldr     r0, = $address
   mcr     p15, 0 , r0, c2, c0, 0
   MEND
 
  요런식으로 만들 수 있겠사옵니다. 부를 때는
  mmu_page_table 0x80000
  뭐 이런식으로 불러쓰면 MACRO가 불리우면서 $address를 0x80000으로 처리해 줘요.
 
뭐~ 대충 이런식인거죠.
 
 ADS VS GNU (gcc)
 
ARM사에서 내 놓은 ADS나 RVCT라는 컴파일러와 GNU진영에서 내놓은 ARM compiler 사이에 Assembly Syntax Grammar는 거의 1:1로 똑같습니다. Assembly 자체는 같다고 보시면 되고요, Directive가 소문자, 그리고 앞에 .으로 시작한다는 것만 다릅니다. (실은 몇 가지는 지원 안 하는 것도 있지만, 거의 같다고 보심되어요) 그 중에 제일 차이 나는 EQU를 예를 들면
 
C Syntax
  #define SDRAM_BASE 0x20000000
ADS(RVCT) Assembly
  SDRAM_BASE    EQU   0x20000000
GNU Assembly
  .equ SDRAM_BASE, 0x20000000
 
뭐 이런식인거죠.
 
또 하나 예를 들어볼까요? AREA 같은 경우는 .globl과 .section으로 표현하는데요,
 
ADS의 경우에는
CODE16
AREA main, CODE, READONLY
 
요렇게 표현한 것을
 
GNU (gcc)의 경우에는
      .global main
    main:
      .section .text, "x" @ x : executable
      .code 16
 
요렇게도 표현한답니다.
 
마지막으로 위에 @가 나왔는데, comment를 GNU에서는 @으로 ADS에서는 ; 으로
단다는 것이 차이가 나요.
 
참고로 GNU Assembly를 한 번 보겠습니다.
 
Helloworld.s -----------------------------------------------------------------------
 
      .globl Helloworld    
Helloworld :
      .section .text, "x" @ x : executable
      .code 32
    
 
BEGIN: @LABEL
            adr r0, THUMB
            bx r0            
 
      .code 16
THUMB: @LABEL
            adr r1, TEXT+1
 
LOOP:  @LABEL
            LDRB r0, [r1], #1    
            CMP r0, #0             
            blne print_ch       
            bne LOOP       
            b EXIT       
 
TEXT: .word  = "Hello World ", 0          ; TEXT → LABEL
           
        .end
 
----------------------------------------------------------------------------------
 
거의 비슷하죠~? 거의 1:1 대응이 되기 때문에 너무 걱정할 필요 없고요, 필요할 때 마다 구글링 해주세요. 웃음꽃이 활짝 필테니까요. ^^ 유훗

by 히언 | 2009/06/30 20:24 | ARM미장센 | 트랙백 | 핑백(1)

Linked at 임베디드 시스템 개발자 되기 .. at 2009/06/30 20:33

... nbsp; ⓣ Make option들 4) ARM 미장센 - ARM 제어의 구현 ⓐ ARM Assemlby를 파헤쳐 보자 ADS VS GNU ⓑ 대충의 간단한 Assembly와 Reverse Engineering ⓒ ARM ... more

◀ 이전 페이지          다음 페이지 ▶