포스트

3. Software 데꾸바쮸(Decoupage) - Software의 정체와 만들기

Embedded Recipes

목차


ⓐ Little Endian과 Big Endian


  • 모든 프로세서는 Little Endian 또는 Big Endian 중 하나를 사용함
  • NUXI 문제. UN IX 라는 단어의 입력의 앞뒤가 바뀌어 NUXI라는 출력이 나와 NUXI문제라고도 함
  • 바이트 오더링
  • Little Endian 은 상위 bit(MSB)를 상위 주소에 저장함
  • Big Endian 은 반대로 낮은 주소부터 읽어와를 상위 주소에 저장함
  • ARM은 Little Endian을 지원. Big Endian 도 쓸 수 는 있음
  • 컴파일러 환경에서 어떤 엔디안을 사용할지 설정 가능

ⓑ 컴파일에 대한 단상


  • Compiler : 책을 편집하다, 안내서를 만들다
  • 프로세서는 전기적인 스위치로 이루어져 있으며, 어떤 특정한 전기 스위치를 작동 시키기 위해서는 데이터 버스 선을 따라 특정 전압을 흘려보내주어야 함
  • 과거에는 이런 특전 전기 신호를 사람이 직접 프로세서에 입력하였음
  • 그나마 사람이 이해할 수 있는 어셈블리언어 사용
    • 어셈블리어를 기계어로 바꾸는 것이 컴파일러의 목적
  • 프로세서마다 다른 어셈블러를 지원하므로, 이를 해결하기 위해 High Level Language Compiler를 제작
    • 동작시키고자 하는 프로세서에 맞는 C compiler를 이용해, 그 프로세서에 동작 가능한 어셈블리를 생성하는 컴파일러
    • C가 이식성이 좋은 이유가, C Compiler가 많이 제공 되기 때문
  • C로 코딩 -> C Compiler로 ARM이 해석할 수 있는 Assembly 제작 -> ARM Assembler를 이용하여 ARM Core가 해석할 수 있는 일련의 Bit Pattern 제작
    • bit parttern 한덩어리를 뭉쳐놓은 것을 Executable binary image라고 함
  • Cross Compile 환경. 실제 타겟에서 돌아갈 binary image를 PC상에서 compile 할 수 있게 해주는 환경
    • 임베디드 시스템에서는 타겟 시스템에서 컴파일을 수행하기에 너무 작은 시스템이라, binary image를 PC상에서 만듬

ⓒ 컴파일 공장 이야기


  • 컴파일러의 목적은 Native code의 집합인 Binary Image를 만들어 내는 것 compile
  • source file(.h, .c, .s 등의 파일)을 이용하여 C Compiler(armcc, tcc)를 이용하여, Assembly로 만들고,
    또 이를 Assembler(armasm)을 이용하여 실행 가능하고, Symbol 정보를 가진 특정한 object type(elf)으로 만든 후, 그안에 있는 Native Code로 이루어진 Binary를 만들어냄
  • link : 여러개의 .c file을 어셈블리로 만든 후, 어셈블러를 이용하여 Object 라는 새로운 기계어 형태를 만들어내, 이런 object들을 연결(link)하는 것
    • object는 각각의 기계어이긴 하지만, 혼자서 완성된 형태는 아니며, 각기 다른 .c 파일을 컴파일 한 object와 연결할 수도 있는 정보와, 디버깅이 가능한 symbol 정보를 담고 있음
    • 이를 elf 형태라고 부르며, elf 형태를 따른 object들은 linker를 이용하여, 서로 link 할 수 있음
  • armcc, tcc는 내부적으로 .c 파일을 .s file로 만들고, 이를 다시 .o(obejct file)로 만들어줌 compile-complex
  • c-compiler로 .c 파일을 컴파일하면
    • 전처리(#define, #include 등을 처리)
    • 전처리된 언어를 mnemonic의 어셈블리로 변환(기계어와 1:1 대응의 .s assembly 로 만듬)
    • Assembly를 실제 기계어로 만듬(elf 형식의 .o file 로 만듬)
  • lib file은 소스코드로 제공되기도하고, object 형식으로 제공되기도 함
  • link 시, scl은 Scatter Loading. binary를 만들 때, 메모리의 주소 구성을 원하는 대로 설정하는 script file
  • Map file이나 sym file은 컴파일된 binary의 메모리 구성을 나타내주는 text file

ⓓ 컴파일 순서에 따라 원하는 컴파일을 해보자


  • C 컴파일러인 tcc 또는 armcc를 이용하여 ARM core에서 동작할 수 있는 Assembly를 만든 후,
    다시 기계어인 .o file만드는데, 이 object file 안에는 ARM이 이해할 수 있는 기계어와 컴파일러가 최종 목적으로 하는 link 단계를 처리할 수 있도록 symbol이 저장되어있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spaghetti.h -----------------------------------------

#define EQUAL =      /- 예제들에서 써먹을려고 일부러 EQUAL이라는 걸 두었다구요 *-  
typedef struct {  bool memberBool;  int memberInt;  word memberWord;  }structure;

spaghetti.c ---------------------------------------------------------  
#include "spaghetti.h" 

int zi EQUAL 0; int rw EQUAL 3;  
extern int relocate EQUAL 3;  
extern structure recipes [3];  
int add(int a, int b);

**main** () {  int stack;  volatile int local,local2,local3;    local EQUAL 3;  locall EQUAL 4;    local2 EQUAL add (local, local2);  stack +EQUAL local3;  return stack;   }  
int **add** (int a, int b) {  return (a+b); }

ⓔ Preprocss (-E option)와 #Include


  • *.c 파일을 *.s 파일로 만들기 전에 Preprocessor(전처리기)를 수행
    • 웬만한 syntax적인 것들을 정리
    • 선언된 macro나, define을 compile하기 전 변환
      • #include, #define, #if, #ifdef
    • syntax에러가 있는지 점검
  • tcc 에서는 -E 옵션을 이용하여 확인 가능. tcc -E spaghetti.c > spaghetti.i 수행 시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spaghetti.i --------------------------------------  
#line 1 "spaghetti.c" #line 1 "spaghetti.h"  
typedef struct {  bool memberBool;  int memberInt;  word memberWord;  }structure;

#line 2 "spaghetti.c"  
int zi = 0; int rw = 3;  
extern int relocate = 3;  
extern structure recipes [3];  
int **add**(int a, int b);  
int **main** () {    
     int stack;    
     volatile int local,local2,loca3;      
     local = 3;    
     locall = 4;      
     localll = add (local, local2);    
     stack += local3;    
     return stack;    
     }  
int **add** (int a, int b) {    
     return (a+b);  
 }
  • spaghetti.h 에 있던 #define EQUAL =이, 모두 처리되어 spaghetti.c의 EQUAL이 모두 =로 변환됨
  • .h와 .c 파일이 합쳐져 .i 파일 생성
  • #include ""#include <>의 차이
    • 어디서부터 찾느냐에 따라 컴파일 속도에 영향을 줌
    • <>는 Compiler에게 predefine 되어 있는 path에서부터 header를 찾음
      • 즉, 보통 C에서 제공하는 standard library들은 ADS의 include에 들어있음
    • ""는.c가 있는 해당 directory에서 부터 시작하여, Search Path로 등록된 path를 찾아감
      • 직접 만든 header들을 include 할 때 많이 사용함
  • 라이브러리를 한번만 include하고 싶을 때, 다음과 같이 선언함
1
2
3
4
#ifndef __SPAGHETTI_H__  
#define __SPAGHETTI_H__  
..............  
#endif /- __SPAGHETTI_H__ *-

ⓕ Assembly로 만드는 방법


  • tcc에서 -s 옵션으로 어셈블리언어로 변환 가능
  • 가끔 아주 간단한 램 상주 프로그램을 올릴 때 사용하기도 함

ⓖ Library - 남한테 보여주기 싫어


  • 미리 컴파일 해놓은 Object file의 모음
  • 자주 사용하는 함수들을 미리 컴파일해두어 Link 할 때 연결만 하면 되게 하여 컴파일 시간을 줄이기 위해서
  • 혹은 소스코드를 공개하지 않기 위해서
  • Static Library, 정적 라이브러리
    • object 자체를 만들어 내서 lib file로 하나로 묶어 버리는 형태
    • link 하면 Object 파일과 마찬가지로 하나의 Binary에 녹아 들어가는 형태
  • 공유 Library
    • Windows에서 DLL 같은 형태로 배포되는 Libarary
  • 보통 임베디드 시스템에서는 정적 라이브러리를 사용함

ⓗ Lib을 까보자


  • 3rd party lib 파일 이용시 까는 방법
  • archive 를 이용해 제작했다면, 다시 까볼 수 있음
  • armar -zs f_t.b : symbol 확인 명령어

ⓘ 컴파일러 Option들


  • tcc(armcc)가 어떤 option들을 제공하는지
  • -o : output 이름 설정
  • -E, -C : Preprocessing 까지만 진행. *.i 파일 만들 때 사용하는 옵션
  • -S : Assembly로 만드는 명령어. *.s파일 출력됨
  • -c : linkable한 object만 만들기. elf 형식의 .o가 생성됨
  • -I, -J : header file link 시 경로 설정 옵션. -I는 dir path에서 찾아서, -J는 원하는 경로 설정 가능
  • -D, -U : Define 옵션. 소스코드 안에 define 하지 않고, 컴파일러에게 직접 define 가능
  • -g : Debugging 정보, DWARF 정보를 ELF file에 넣어두기
  • -w : Warning Level 을 결정할 수 있는 옵션
  • -O1, -O2 : 컴파일 결과물을 최적화를 얼마나 할 것인지
  • -l : 라이브러리 이름을 주게 되면, 특정한 library를 link할 때 참고해서 link 함
  • -L : Library를 검색할 경로를 옵션으로 줄 수 있음

ⓘ 변수의 scope와 그 생애 (Memory Map)


  • C의 관점에서, 실제 메모리에서 어떻게 운용되는지
  • 변수의 유형들 auto, extern, static, volatile
  • Local, Global variable 은, 변수의 scope와 생존기간을 모두 포함
  • auto 변수 유형 - LOCAL 변수
    • 자신이 선언된 함수 또는 block({})
    • 함수의 수행이 종료되면 return과 동시에 사라짐
  • extern 변수 유형 - GLOBAL 변수
    • 함수 바깥쪽에 정의됨
    • 변수가 선언된 파일 전체에 영향을 끼침
    • 프로그램 어느 부분에서도 사용할 수 있으며, 심지어 다른 파일에서도 불러다 쓸 수 있음
  • STATIC 변수 유형
    • local static : 함수가 끝나더라도 그 값을 유지하고 있음
    • global static : 다른 파일에서 extern을 선언하여 가져갈 수 없는 변수.
      • 큰 프로젝트 수행 시, 자신의 file에서만 사용하고, 다른 사람이 가져다 쓰지 못하게 하는데 사용하는 경우가 많음. C++의 protect와 같은 원리
  • VOLATILE 유형
    • 컴파일러의 최적화를 막기 위해 사용하는 변수
    • 디바이스 드라이버를 디자인할 때 많이 사용함. Memory Mapped I/O의 경우, 같은 주소에 다른 값들을 연속해서 쓰는 경우가 많기 때문

ⓚ Memory Map과 Symbol 이야기


  • Symbol
    • Linker가 알아볼 수 있는 기본 단위
    • Link한 후 자신만의 주소를 가지게 되는 특별한 단위
    • 심볼의 이름은 그 심볼이 갖는 메모리 영역의 시작 주소를 가리키는 Linker만의 포인터임
    • 디버깅시 이런 심볼의 이름이 사용되기도 하며, 전역변수의 이름이나 함수의 이름이 그 예
    • Linker를 위해 ELF object 파일 내에는 Symbol table이 존재
      • 소스 코드에 의해 참조되는 심볼들의 이름과 위치 정보가 들어있음
      • 다른 파일에서 정의된 심볼을 가져다 쓰는 경우에는 해당 파일에 심볼이 없기 떄문에 그 Obj 파일에서 Symbol table은 불완전하게됨
        • 이런 불완전한 심볼들은 linker에 의해 처리하여 다른 파일에 있는 심볼들을 연결하여 사용할 수 있게 만들어줌
    • 실제로 메모리에 적재되지는 않음
    • 자기 자신만의 주소를 갖지 못하는 것들은 local 변수
    • 자기 자신만의 주소를 갖는 것들은 Global(함수, 전역변수, static 변수) 로 이해하면 쉬움

img

  • RW : read-write. 초기값이 있는 변수
  • ZI : Zero-initalized. 초기값이 0인 전역변수. RAM 에 위치
  • RO : Read only. 수정이 불가능한 const 전역 변수와 text인 code. ROM에 위치

ⓛ ELF format Object File에 관한 진실. -c option (기계어 세상)


  • ELF : Executable and Linking Format
    • 실행 가능한 그리고 링크를 하는 형식
    • -c 옵션을 통해 tcc로 relocatable file 생성

elf

  • Linking View가 relocatable file 형식
    • 링크하기전 오브젝트 파일은 Linking View, 링크 이후 완전한 실행 가능한 형태가 된 ELF 형식을 Execution View라고 함
  • ELF Header에는 Elf file의 특징을 결정 짓는 인디안, 운영체제, CPU 정보, 내부의 각 Section의 시작 위치 등이 들어있음
  • GCC bin utility 의 readelf 라는 유틸리티를 통해 헤더를 쉽게 읽을 수 있음
  • fromelf 명령어를 통해 disassemble 할 수 있음

ⓜ Linker를 마무리 짓자 - ELF와 fromelf 까지!


  • Linker를 이용해 executable ELF format image를 만드는 과정
  • Linker는 모든 input object 파일들의 모든 코드와 데이터를 가지는 실행 가능한 새로운 object 파일을 만들어냄

linker

  • Linker Placement rule. 이를 위해 각각의 object 파일들이 가지는 text, bss, data를 모아 새로운 text, bss, data를 만듬
  • Link시에 실제 함수 정의부의 위치와 전역변수들의 위치를 library file과 object file에서 차례대로 조사한 후에 모두 테이블로 가지고 있다가, 그 주소를 함수호출 코드 부분에 기록해 넣는 것이 Lnker가 하는 일

elf

  • ELF format relocatable object file 구조

elf2

  • 같은 속성들의 section들을 모아서 한 segment안에 다 넣음

  • code segment 중, text, rodata를 RO, data를 RW라는 두 개 section으로 나눈 binary 형태로 만들어야 임베디드 시스템에서 실행될 수 있는 binary가 만들어짐

ⓝ Scatter Loading/ Linker Description Script와 메모리 다루기


  • XIP : Execution In Place. 메모리 대신 플래시에 소프트웨어를 적재해서 직접 수행하는 기술
    • Code 영역이 자리잡을수 있어야 가능한 기술

scatter

  • Scatter Loading
    • Input Sections, output Sections, Region
    • Input Sections들을 모아놓은게 Output Sections
    • Output Sections들을 모아놓은게 Region. 실제 메모리에 올라가는 단위
      • RO, RW, ZI 순으로 알아서 sorting됨
    • Scatter Loading을 위해서는 input section, output section만 표시해주면, Linker가 알아서 region에 맞게 Output Sections을 생성해줌

scatter2

  • Load View : 소프트웨어가 실행 되기 전에 저장매체에 담겨 있을때의 모습
    • Flash에 실행 이미지가 담겨있을 때의 형태. ROM에 적재 되어있을때
  • Execution View : 소프트웨어가 실행될 떄의 모습
    • RW를 위해서 RAM으로 복사됨

ⓞ Map file 분석


  1. Image Symbol Table
    • 링커가 만들어낸 심볼과 주소 그리고 리전
    • 유저가 만들어낸 심볼과 주소 크기 그리고 속해있는 오브젝트
  2. Memory Map of the image
    • Scatter Loading에 맞춘 리전에 따라 구획을 나누어서
    • 주소와 사이즈, 타입, 그리고 섹션과 오브젝트
  3. Image Component size
    • 각 오브젝트 또는 라이브러리에 대한 RO,RW,ZI가 차지하는 크기
  4. 전체 Layout
    • 전체적인 메모리에 RO,RW,ZI가 얼마나 차지하는지에 대한 정보 - 보통 Image Symbol Table을 찾아 거기에서 심볼을 찾아 어디에 위치하는지 검색할때 사용 - 혹은 마지막 전체 레이아웃에서 메모리 양이 맞게 되어있는지를 확인할 때 사용

ⓟ Memory Map과 Linker의 만남 - Locator


  • 문제1) embedded.c와 recipes.c를 32bit ARM mode로 컴파일 하여 object를 생성하고, main.s를 컴파일 하여 object를 생성하여, R0의 위치를 0x10000, RW의 위치를 0x20000에 relocate 시키고 싶어요. 어떻게 하면 좋을까요? 단, output은 elf 형식이어야 함.
  • 답)
    1
    2
    3
    
    armcc -c embedded.c recipes.c  
    armasm main.s  
    armlink -elf -ro-base 0x0 -rw-base 0x10000 -o target.elf embedded.o recipes.o main.o
    
  • 문제2) embedded.c와 recipes.c를 ARM mode로 컴파일 하고요, boot.s를 컴파일 하는데, Load Region은 0x0에서 부터 시작하고, Execution Region에 boot.s의 Int_Vect를 역시나 0x0에 locate하고 싶어요. 그리고 나머지 RO를 Execution Region에 넣고 싶고요, 그리고 두 번째 Execution Region도 만들어서, 시작은은 0x4000에서부터 시작하게 하면서 embedded.c와 recipes.c의 RO를 넣고 싶고요. 마지막으로 세 번째 Execution Region을 만들어서 시작은 0x8000에서부터 시작하고 embedded.c, recipes.c, boot.s의 RW, ZI를 몽땅 집어 넣고 싶답니다 어떻게 하면 좋을까요? 단, output은 elf이어야 함.
  • 답)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    LOAD_REGION 0x0 // Load Region이 0x0에서 시작하고요,  
    {  
       EXE_REG_1 0x0 // 첫 번째 Execution Region이 0x0에서 시작하고요.  
       {  
          boot.o (+RO, Int_Vect) // boot.o의 RO가 들어가는데, 그중에 Int_Vect Label이 젤 먼저 들어가구요.  
       }  
       EXE_REG_2 0x4000 // 두 번째 Execution Region이 0x4000에서 시작하고요.  
       {  
          embedded.o (+RO) // 나머지 두개의 c file의 RO들이 자리 잡고요.  
          recipes.o (+RO)  
       }  
       EXE_REG_3 0x8000 // 세 번째 Execution Region이 0x8000에서 시작하고요.  
       {  
          * (+RW, +ZI) // 3개의 object에서 뽑아낸 RW, ZI가 모두 이곳에 자리 하고요.  
       }  
    }
    
1
2
3
armcc -c embedded.c recipes.c  
armasm main.s  
armlink -elf -scatter Target.scl -o target.elf embedded.o recipes.o main.o

ⓠ Makefile은 뭘 하는 녀석일까~?


  • 반복적인 컴파일 작업을 자동화 하는 유틸리티
  • DOS에서 사용하는 script file인 batch file과 유사함

ⓡ 컴파일을 더더더 쉽게. MACRO와 SUFFIX


  • MACRO는 makefile에서도 사용할 수 있음 방법들
    1. MACRO로 간단히 만들기
    2. Link 없애기
    3. $@, $^ 사용
    4. SUFFIX Rule 이용
    5. 디렉토리를 한번에 컴파일

ⓢ 조금 더 Make 테크닉들


  • Make를 더 효율적으로 사용하는 테크닉들 다루는 챕터
  • 필요할 때 찾아볼 것

ⓣ Make Option들


  • Make의 세부 옵션들
  • 필요할 떄 찾아볼 것

Memory Map 파트부터 Scatter Loading 파트는 이해가 잘 되지 않아 다시 읽어봐야할 것 같다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.