3. Software 데꾸바쮸(Decoupage) - Software의 정체와 만들기
목차
- ⓐ Little Endian과 Big Endian
- ⓑ 컴파일에 대한 단상
- ⓒ 컴파일 공장 이야기.
- ⓓ 컴파일 순서에 따라 원하는 컴파일을 해보자 -
- ⓔ Preprocss (-E option)와 #Include (“” <>) -
- ⓕ Assembly로 만드는 방법
- ⓖ Library - 남한테 보여주기 싫어
- ⓗ Lib을 까보자
- ⓘ 컴파일러 Option들
- ⓘ 변수의 scope와 그 생애 (Memory Map)
- ⓚ Memory Map과 Symbol 이야기
- ⓛ ELF format Object File에 관한 진실. -c option (기계어 세상)
- ⓜ Linker를 마무리 짓자 - ELF와 fromelf 까지!
- ⓝ Scatter Loading/ Linker Description Script와 메모리 다루기.
- ⓞ Map file 분석
- ⓟ Memory Map과 Linker의 만남 - Locator
- ⓠ Makefile은 뭘 하는 녀석일까~?
- ⓡ 컴파일을 더더더 쉽게. MACRO와 SUFFIX
- ⓢ 조금 더 Make 테크닉들
- ⓣ Make Option들
ⓐ 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를 만들어 내는 것
- 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)로 만들어줌
- 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과 동시에 사라짐
- 자신이 선언된 함수 또는 block(
- 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 변수) 로 이해하면 쉬움
- 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 생성
- 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 Placement rule. 이를 위해 각각의 object 파일들이 가지는 text, bss, data를 모아 새로운 text, bss, data를 만듬
- Link시에 실제 함수 정의부의 위치와 전역변수들의 위치를 library file과 object file에서 차례대로 조사한 후에 모두 테이블로 가지고 있다가, 그 주소를 함수호출 코드 부분에 기록해 넣는 것이 Lnker가 하는 일
- ELF format relocatable object file 구조
같은 속성들의 section들을 모아서 한 segment안에 다 넣음
code segment 중, text, rodata를 RO, data를 RW라는 두 개 section으로 나눈 binary 형태로 만들어야 임베디드 시스템에서 실행될 수 있는 binary가 만들어짐
ⓝ Scatter Loading/ Linker Description Script와 메모리 다루기
- XIP : Execution In Place. 메모리 대신 플래시에 소프트웨어를 적재해서 직접 수행하는 기술
- Code 영역이 자리잡을수 있어야 가능한 기술
- 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을 생성해줌
- Load View : 소프트웨어가 실행 되기 전에 저장매체에 담겨 있을때의 모습
- Flash에 실행 이미지가 담겨있을 때의 형태. ROM에 적재 되어있을때
- Execution View : 소프트웨어가 실행될 떄의 모습
- RW를 위해서 RAM으로 복사됨
ⓞ Map file 분석
- Image Symbol Table
- 링커가 만들어낸 심볼과 주소 그리고 리전
- 유저가 만들어낸 심볼과 주소 크기 그리고 속해있는 오브젝트
- Memory Map of the image
- Scatter Loading에 맞춘 리전에 따라 구획을 나누어서
- 주소와 사이즈, 타입, 그리고 섹션과 오브젝트
- Image Component size
- 각 오브젝트 또는 라이브러리에 대한 RO,RW,ZI가 차지하는 크기
- 전체 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에서도 사용할 수 있음 방법들
- MACRO로 간단히 만들기
- Link 없애기
$@,
$^
사용- SUFFIX Rule 이용
- 디렉토리를 한번에 컴파일
ⓢ 조금 더 Make 테크닉들
- Make를 더 효율적으로 사용하는 테크닉들 다루는 챕터
- 필요할 때 찾아볼 것
ⓣ Make Option들
- Make의 세부 옵션들
- 필요할 떄 찾아볼 것
Memory Map 파트부터 Scatter Loading 파트는 이해가 잘 되지 않아 다시 읽어봐야할 것 같다
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.