이 장에서는 운영체제 개발을 위해 필요한 툴들에 대해 하나씩 상세히 소개한다. 대표적인 툴로는 vmware 와 bochs 가 있다. 물론 이외에도 많은 도구들이 있으나, 필자가 경험해 본 것들 중에서는 bochs 와 vmware 정도면 충분했다. vmware 는 상용으로 판매되는 소프트웨어로 신뢰도가 매우 높았다 물론 실제 장비에서 돌려보는 것 만큼 확인하기 좋은 도구는 없겠지만, vmware 를 이용한 간편한 확인은 개발 속도를 향상시켜 줄 수 있을 것이다.
2.1 Bochs
Bochs 는 C++ 로 개발된 open source IA-32(x86) PC emulator 이다. 이 에뮬레이터는 Intel x86 CPU 와 일반적인 I/O 장치들 그리고 BIOS 를 포함하고 있다. 현재는 386, 486, Pentium CPU 를 emulate 하기 위해 컴파일 될 수 있다. Bochs 는 Linux, Window 95, Dos 와 Window NT 4 를 emulate 시스템에서 동작시킬 수 있다. Kevin Lawton 이 개발을 시작했으면,
http://bochs.sourceforge.net 에서 프로젝트를 관리하고 있다. Bochs 는 다양한 모드로 컴파일 될 수 있다. 전형적으로 x86 processor 와 하드웨어 장치, 메모리를 포함한 완전한 x86 PC 를 에뮬레이트를 지원한다. 이러한 기능은 독자의 workstation 기계 안에 또 다른 기계를 가상화 시켜 OS 나 각종 Software 를 동작시킬 수 있게 한다. 예를 들어 Window 95 application 을 Solaris 장치의 X11 에서 동작시킬 수 있다. BOCHS 는 GNU LGPL 라이선스로 배포된다.
2.1.1 소스코드
http://bochs.sourceforge.net 에서 다운로드 받을 수 있다. 또는 CVS 를 통해 가장 최신의 소스를 얻을 수 도 있다.
2.1.2 컴파일
모든 릴리즈는 gzip’d tar 파일이다 즉, bochs 소스코드들은 tar 와 gunzip, gzip 툴들을 이용해 하나의 파일로 묶여 있다. 압축을 풀기 위해서는 이 두가지 툴이 필요하다. 모든 파일은 ‘bochs-YYMMDDv’ 라는 이름의 서브디렉토리 안에 모두 들어 있다.cd /path/parent-directory gzip –dc bochs-YYMMDDv.tar.gz | tar –xvf – cd bochs-YYMMDDv컴파일하기 전에, configure script 를 실행시켜야 한다. Configure 스크립트는 소소코드가 독자의 시스템에서 선택한 옵션에 맞게 동작할 수 있도록 만든다. configure 는 수 많은 옵션들을 제공하고 있으며, 독자 중에는 모든 옵션이 필요 없을지도 모른다. 예를 들어, BeOS 를 실행시키고 싶다면, ‘—with-beos’ 를 configure 스크립트에 명시해 주면 된다. 전체 옵션을 모두 보고 싶다면, 다음과 같이 실행하면 된다../configure –helpconfigure 스크립트는 독자의 환경에 맞는 컴파일러 옵션과 컴파일러가 사용되도록 Makefile 을 구성한다. 독자는 configure 스크립트를 실행 하기 전에 다음 4 가지 환경변수를 설정해야 한다. bash 를 예로 든다면,CC=’egcc’ CXX=’$CC’ CFLAGS=’-Wall –O2 –m486 –fomit-frame-pointer –pipe’ CXXFLAGS=’$CFLAGS’export CC export CXX export CFLAGS export CXXFLAGSconfigure 스크립트는 다음과 같이 실행 시킬 수 있으며, 자세한 옵션은 아래 테이블에서 설명한다../configure 또는 ./conf.x86자주 사용되는 옵션들은 다음과 같다.
| Option | Default | Comments |
| –enable-cp-level={3,4,5} | 5 | 에뮬레이트 하기 위한 CPU 를 선택한다. 3은 386, 4 는 486, 5는 Pentium 을 각각 emulate 한다. |
| –enable-processors={1,2,3,…,15} | 1 | 1 보다 큰 수로 바꾸면, SMP 시뮬레이션을 사용할 수 있게 된다. 이것은 Linux 와 다른 OS 들을 SMP 모드로 실행 시킬 수 있게 해준다. 특히 bochs 는 각각의 CPU 를 서로 다른 것으로 에뮬레이트 할 수 있고, 각각 서로 통신할 수 있게 지원한다. 이 옵션이 처리 속도가 빨라질 것이라고 기대하는 독자는 없길 바란다. (반대로 이 옵션은 시뮬레이션 속도를 더 느리게 할 수 있다.), SMP 를 실험해 보고 싶은 독자들에게 권하는 옵션이다. |
| –enable-cdrom | No | 실제 CDROM 을 활성 시킨다. 기본적으로는 media 가 없어도 drive 를 에뮬레이트 하고 항상 emulate 상태에서는 CDROM 이 존재하는 것처럼 동작한다. 독작의 workstation 의 실제 CDROM 을 사용하고자 한다면 이 dhtuqs을 사용하면 된다. 현재 Linux, Solaris, OpenBSD 와 Windows 플랫폼에서 지원되면, 만약 다른 플랫폼에서 사용하고 싶다면, iodev/cdrom.cc 파일을 수정하면 된다. 대부분이 ioctol() 함수 호출을 올바르게 할 수 있도록 수정하는 것이다. |
| –enable-sb16={dummy, win, linux} | no | Sound blaster 를 에뮬레이트한다. SB16 출력은 Window 와 Linux 에서만 지원되면, dummy 옵션은 SB16 을 지원하지만, 출력 장치를 사용하지는 않는다. |
| –enable-cpp | no | .cc 파일들을 .cpp 파일로 바꾼다. |
| –enable-debugger | No | Bochs 의 내부 command line 기반의 debugger 를 활성시킨다. X86 하드웨어 디버깅과는 관련이 없다. 실제 emulation 에는 아무런 영향을 주지 않는 강력한 디버깅 도구이다. ./configure 를 실행한 후 config.h 파일을 수정하여 디버거를 최적화 시킬 수 있다. |
| –enable-disasm | No | Disassembler 를 컴파일한다. Bochs 는 내부에 disassemble 를 가지고 있다. –-enable-debugger 옵션을 사용한 경우, 유용하다. |
| –enable-vga | Yes | VGA Emulation 사용한다. 기본 옵션 |
| –enable-fpu | Yes | Bill Metzenthen 이 쓴 FPU emulation 을 사용할 수 있다. |
| –enable-x86-debugger | No | X86 debugger 를 지원한다. Bochs 에서 실행하는 소프트웨어가 x86 hardware debugging 용인 DR0 – DR8 와 같은 레지스터와 명령 및 데이터 브레이크 포인트를 사용하고 싶다면 이 옵션을 쓰면된다. |
| –with-x11 | Yes | X11 GUI 사용 |
| –with-rfb | No | AT&T 의 VNC Viewer 를 사용하기 위해 RFB Protocol 을 사용한다. RFB 코드는 Don Becker 가 썼으며, http://www.psyon.org/bochs-rfb 에서 확인할 수 있다. |
| –with-beos | No | BeOS GUI 를 사용한다. BeOS 를 사용하는 경우 쓴다. |
| –with-win32-vcpp | No | Win32 GUI 와 Visual C++ 환경에서 사용한다. Configure 스크립트를 MS Win32/Visual C++ 환경에 맞게 수정해야 한다. |
| –with-macos | No | Machintosh 와 Code warrior 환경을 사용한다. |
| –with-nogui | No | GUI 를 쓰지 않는다. |
기타 옵션들로는 다음과 같은 것들이 있다.
| Option | Default | Comments |
| –enable-ne2000 | No | 제한된 ne2000 을 사용한다. 각 OS 에서 저 수준의 컴포넌트를 요구할 때 사용한다. FreeBSD 를 위해 작성된 것이 있다. 아직 완벽하지 않기 때문에 사용하는 것을 추천하지 않는다. |
| –enable-pci | No | 제한된 i440FX PCI 를 사용한다. 아직 완벽하지 않으므로, 사용하는 것을 추천하지 않는다. |
| –enable-port-e9-hack | No | 콘솔로 가기 위해 port e9 를 사용한다. |
| –enable-loader | No | 디버거에서 외부 로더를 사용한다. 아직 지원되지 않는 옵션이다. |
| –enable-instrumentation | No | Instrument 를 사용한다. Bochs 의 실실행코드부터 instrumentation 데이터를 수집할 때 사용한다. Instrument 를 사용하기 위해 직접 instrument code 를 작성해야 한다. |
위 옵션들을 이용해 설치가 완료되면 실행을 시키기 위한 환경설정을 해야 한다.
2.1.3 실행
Option 1. VGA 폰트를 전체 폰트 시스템 디렉토리에 설치하는 방법
cp font/vga.pcf font-path-here/misc
compress font-path-here/misc/vga.pcf
mkfontdir font-path-here/misc
xset fp rehash
/etc/rc.d/init.d/xfs restart
독자가 가진 시스템에 맞도록 폰트를 복사하고, 독자가 사용하는 폰트 시스템이 새로 설치한 폰트를 찾을 수 있도록 하면 된다. 위 예는 Redhat 시스템 예제이다.Option2. VGA 폰트를 로컬 폰트 시스템 디렉토리에 설치하는 방법
mkfontdir bochs-YYMMDD/font
xset fp+ [full-path]bochs-YYMMDD/font
Hard Drive Image File: Bochs 는 hard drive 를 용량이 큰 파일로 에뮬레이트한다. Hard 드라이브의 크기는 cylinder, heads, 와 track 당 sector 수로 결정된다. 아래에 가능한 drive geometry 예가 있다.
| Size | Cylinders | Heads | Sectors/Track | Sector Total |
| 10MB | 306 | 4 | 17 | 20808 |
| 20MB | 615 | 4 | 17 | 41820 |
| 30MB | 615 | 6 | 17 | 62730 |
| 46MB | 940 | 6 | 17 | 95880 |
| 62MB | 940 | 8 | 17 | 127840 |
| 112MB | 900 | 15 | 17 | 229500 |
| 126MB | 256 | 16 | 63 | 258048 |
| 483MB | 1024 | 15 | 63 | 967680 |
| 504MB | 1024 | 16 | 63 | 1032192 |
| 640MB | 1300 | 16 | 63 | 1310400 |
| 886MB | 1800 | 16 | 63 | 1814400 |
| 1280MB | 2600 | 16 | 63 | 2620800 |
| 2215MB | 4500 | 16 | 63 | 4536000 |
| 3495MB | 7100 | 16 | 63 | 7156800 |
| 5020MB | 10200 | 16 | 63 | 10281600 |
| 7088MB | 14400 | 16 | 63 | 14515200 |
| 10041MB | 20400 | 16 | 63 | 20563200 |
| 12206MB | 24800 | 16 | 63 | 24998400 |
| 16045MB | 32600 | 16 | 63 | 32860800 |
| 20672MB | 42000 | 16 | 63 | 42336000 |
| 25003MB | 50800 | 16 | 63 | 51206400 |
| 29974MB | 60900 | 16 | 63 | 61387200 |
| 32238MB | 65500 | 16 | 63 | 66024000 |
하드 디스크의 크기가 528 MB 보다 큰 경우, newharddrivesupport 가 설정되었는지 확인해야 한다. .bochsrc 파일에 newharddrivesupport: 라는 항목에 enabled=1 이 있으면 된다. 이것은 1024 cylinders 보다 큰 경우를 지원하기 위해 사용되는 옵션이다.에뮬레이트 하고 싶은 크기의 하드 드라이브를 선택한 후, 빈 파일을 위와 같은 옵션에 맞춰서 만든다. dd 유틸리티를 사용해서 파일을 만들 수 있다. 섹터의 크기가 512 Bytes 라는 것을 숙지하고, Sectors Total 필드에 있는 값을 dd 옵션의 count= 에 지정해주면 된다. Block size 로는 512 bytes 로 지정한다. 예를들어 112 MB 드라이브를 만드는 경우에는 229500(900*15*17) 섹터를 필요로 한다.dd if=/dev/zero of=112MB bs=512 count=229500Floppty Disk Image File: Bochs 는 floppy drive/disk 를 파일을 이용해 emulate 하거나 실제 플로피 디스크 드라이브를 사용할 수 있다. .boshrc 에 실제 장치 파일 또는 파일 이름을 지정해주면 된다. ‘floppya:’ 또는 ‘floppyb:’ 지시자를 사용하면 된다.
| SizeCylinders | Heads | Sectors/Track | Sectors Total |
| 0.720MB | 80 | 2 | 9 | 1440 |
| 1.2MB | 80 | 2 | 15 | 2400 |
| 1.44MB | 80 | 2 | 18 | 2880 |
| 1.680MB | 80 | 2 | 21 | 3360 |
빈 플로피 이미지 파일을 만들기 위해서는 하드 디스크 이미지 파일을 만들었던 방식대로 하면 된다. 예를 들어 1.44MB 이미지 파일을 만들고 싶다면,
dd if=/dev/zero of=1.44M bs=512 count=2880
실제 플로피 디스크를 사용하고 싶다면, 독자의 시스템에서 해당 디스크 드라이브에 접근할 수 있는 권한을 가지고 있는 환경에서 /dev/fd0 디바이스 노드 경로를 사용하면 된다.Bochsrc 설정 파일 옵션독자가 직접 변경할 수 있는 많은 종류의 옵션이 존재한다. 이러한 옵션을 ‘.boshrc’ 라는 파일 안에서 설정할 수 있다. 해당 파일의 사용 예제는 배포되는 bochs 소스 압축 파일에서 찾을 수 있다. Bochs 는 해당 설정 파일을 다음과 같은 순서로 검색해서 사용한다.실행하는 현재 디렉토리에서 찾는다. 독자의 홈 디렉토리에서 찾는다. 찾는 것을 포기하고, 사용하지 않는다.Bochs 실행 옵션.boshrc 에서 사용할 수 있는 옵션들은 독자가 직접 명령 옵션으로 지정할 수 있다. 설정 문법은 .boshrc 에서 사용했던 것과 일치한다. 예를 들면 다음과 같다.
bochs boot:c
bochs “boot: c” “diskc:
file=../10M.vga, cyl=306, heds=4, spt=17”
bochs romimage:/tmp/someimage
2.1.4 Debugger 사용
Bochs 를 debugger 모드로 설치한 경우, 시작과 함께 디버깅 모드로 진입한다. 시스템의 시작에서부터 하나씩 디버깅을 할 수 있는데, 실제 우리가 필요한 것은 부트 로더 부터이므로, 0x7c00 에서부터 디버깅을 시작하면 된다.다음은 bochs 를 디버깅 모드에서 수행하기 위한 간단한 명령어 셋이다.
| Command | Example | Description |
| Help | Help | 도움말을 보여준다. |
| B [mem]B 0x7c00 | 0x7c00 에 break point 를 설정한다. |
| S | S | 한 스텝씩 명령을 수행한다. |
| N | N | 하나의 instruction 단위로 수행하되 현재의 흐름을 벗어나지 않는다. |
| X /nuf [mem] | X /100bx | N : number, u : unit, f: format |
| Blist | Blist | 설정된 breakpoint 목록을 보여준다. |
| D [num] | D 1 | 지정된 번호에 해당하는 break point 를 삭제한다. |
| Pb [mem] | Pb 0x7c00 | 0x7c00 에 지정된 break point 를 제거한다. |
| U [mem] [mem] | U 0x7c00 0x7c200 | 0x7c00 부터 0x7c20 에 해당하는 메모리 내용을 덤프한다. |
이 외에 show 명령을 이용해서 IDT, GDT 등을 화면에 출력 할 수 있다.Bochs 는 디버깅되는 모든 내용이 설정파일에 설정된 파일 이름으로 출력된다. 디버깅을 마친 후 해당 파일을 열어 보면, bochs 가 실행될 때부터 실행 되었던 디버깅 명령이 나타나게 된다.일반적으로 디버깅을 할 때 가장 큰 도움이 되는 것은, 심볼을 보면서 디버깅 하는 것이다. 필자는 귀차니즘에 한 동안 이 기능을 쓰지 않고, 오로지 주소값만 가지고 현재 실행되는 위치를 매번 직접 찾아서 확인했다.하지만, bochs 는 부족하지만, 아쉬운대로 쓸만한 심볼 적재 기능을 제공한다.Symbol file 은
형식으로 작성 하면 되고,bochs debugger 로 로딩하기 위해서는
이라는 명령을 사용한다. 이 때 파일 이름은 반드시 겹따옴표로 감싸여 있어야 한다. (필자는 이걸 몰라서, 한 참동안 맨붕에 빠져 있었다)그리고 "continue" 를 하면, bp 를 잡을 때 마다 현재 실행 중인 곳을 심볼 이름으로 확인할 수 있다.
2.1.5 설정 파일 예
다음은 필자가 사용하는 bochs 의 설정파일이다.
romimage:
file=$BXSHARE/BIOS-bochs-latest, address=0xf0000
megs: 32
vgaromimage: $BXSHARE/VGABIOS-elpin-2.40
# 플로피 디스크 드라이브만 사용할것이다.
floppya: 1_44=/root/workspace/ssm/ksig/kos/floppy144.img,
ata0: enabled=0, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata1: enabled=0, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata2: enabled=0, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11
ata3: enabled=0, ioaddr1=0x168, ioaddr2=0x360, irq=9
boot: floppy
ips: 1000000
floppy_bootsig_check: disabled=0
log: bochsout.txt
panic: action=ask
error: action=report
info: action=report
debug: action=ignore
debugger_log: debugger.out
vga_update_interval: 300000
keyboard_serial_delay: 250
keyboard_paste_delay: 100000
floppy_command_delay: 500
mouse: enabled=0
private_colormap: enabled=0
keyboard_mapping: enabled=0, map=
ld 는 ldscript 라는 기능을 제공한다. ldscript 는 텍스트 파일로 이미지의 내부 구조에 대한 재배치를 할 수 있게 도와준다. 커널 이미지의 재배치는 반드시 필요한데, 커널 이미지를 메모리에 적재하였을 때, Entry Point 의 위치를 지정해 주어야 하기 때문이다.Section 들을 재배치 하고, output format 을 결정한다.
OUTPUT_FORMAT(“binary”)
ENTRY(entry)
phys = 0x7e00;
SECTIONS
{
.text phys : AT(phys) {
code = .;
(.text)
. = ALIGN(4096);
}
.bss : AT(phys + (bss – code))
{
bss = .;
(.bss)
bssend = .;
. = ALIGN(4096);
}
.data : AT(phys + (data – code))
{
data = .;
(.data)
(.rodata*)
. = ALIGN(4096);
}
end = .;
}
이 script 에 따르면, output format 은 binary 즉, 특별한 형식이 없으며, 물리적으로 시작될 주소는 0x7e00 라고 명시하고 있다. 제일 처음 Code 영역이 나오고 이어서 BSS 영역, 마지막으로 DATA 영역이 나오도록 섹션들을 재배치 하고 있다.이렇게 되면, Linking 과정까지 마치고 나온 최종 바이너리 파일의 0 번째 offset 에서부터 코드가 바로 시작될 것이며, 모든 심볼들은 0x7e00 에 적재되었을 때를 기준으로 주소가 결정되어 있다.
- 주의
- 그럼, 독자 중에는 이런 궁금증이 생길 수 도 있다. "그럼,. 우리가 쓰는 ELF 에 대한 ld 도 있을까?" "있다." 필자의 경우, "/usr/lib/binutils/i686-pc-linux-gnu/2.23.1/ldscripts/" 이곳에 모여 있고, 독자들도 마찬가지로 비슷한 위치에 있을 것이다. 가운데 2.23.1 과 i686-pc-linux-gnu 타입이 약간 다를 수 있으니, 잘 찾아보면 된다.
4.1.1 Using ld
이 장을 시작하기 전에, Steve Chamberlain 이 쓴 "Using ld" 라는 문서를 참조 하였음을 밝혀 둔다. ld version 2 로 1994년 1 월에 작성된 것의 3장 일부를 번역하여 옮겼다.
4.1.2 Command Language
Command Language 는 링크 과정을 명시적인 방식으로 제어할 수 있도록 지원하는 것으로, 링커의 입력 파일과 출력 파일 사이에서 아래의 것들을 제어할 수 있다.
-
입력 파일
-
파일 형식
-
출력 파일 구조(layout)
-
섹션 주소
-
공통 블록의 위치 지정
링커 옵션 중 –T 를 이용해서 명령 파일 (일반적으로 링커 스크립트 라고 한다) 을 명시적으로 지정할 수 있다. 물론 대부분의 경우는 시스템과 함께 배포된 정규 스크립트를 사용하게 된다. 하지만 이 장을 읽고 있는 독자라면, -T 옵션을 자주 사용하게 될 것이다.
4.1.3 Linker script
ld Command Language 는 문장들의 집합이다. 간단한 키워드 몇 가지는 특별한 옵션을 지정하거나, 입력 파일 그룹, 출력 파일 이름 등을 지정한다. 가장 기본적인 ld Command Language 는 SECTIONS 명령이다. 의미 있는 모든 명령 스크립트는 반드시 SECTIONS 명령을 가지고 있어야 한다. SECTIONS 는 출력 파일의 layout 을 결정하는 역할을 한다. MEMORY 명령은 타겟 아키텍쳐의 사용 가능한 메모리 영역을 기술하여 SECTIONS 명령을 보완한다. 이 명령을 사용하지 않으면, ld 는 필요한 메모리가 연속되는 블록들에 존재하는 것으로 인지한다. C 언어서 주석 처리를 하듯이 링커 스크립트에서도 /* *\/ 를 이용해 주석 처리를 할 수 있다.
4.1.4 Expressions
많은 수의 유용한 명령들은 수학적 표기법과 연관되어 있다. 수식 표현의 문법은 다음 특징들을 따라 C 에서의 표현과 일치한다.
-
모든 수식은 정수형의 unsigned long 이나 long 으로 처리된다.
-
모든 상수는 정수형이다.
-
모든 종류의 C 수식 연산자가 제공된다.
-
전역 변수를 생성, 정의, 참조 할 수 있다.
-
특수 용도의 내장 함수를 호출할 수 있다.
4.1.5 Integers
0 으로 시작하는 수는 8 진수로 인식한다._as_octal = 01257255;10진수는 0이 아닌 숫자로 시작한다._as_decimal = 57005;16 진수는 0x 또는 0X 로 시작한다._as_hex = 0xdead;음수는 앞에 – 부호를 붙인다._as_neg = -57005;추가적으로 K 와 M 으로 끝나는 숫자는 각각 1024 와 10242 를 곱한 것과 같다. 예를들어 다음 세 문장은 모두 같은 값이다._fourk_1 = 4K; _fourk_2 = 4096; _fourk_3 = 0x1000;
4.1.6 Symbol Names
따옴표로 묶이지 않은 심볼 이름은 글자,밑줄,점등으로 시작해야 하며 밑 줄, 숫자, 점, 하이픈, 문자등으로 구성될 수 있다. 하지만, 예약어는 사용할 수 없다. 이중 따옴표를 이용하면, 공백 문자를 포함한 이름 또는 예약어도 심볼로 정의해서 사용할 수 있다.“SECTION” = 9; “with a space” = “also with a space” + 10;또한, 심볼은 영문자가 아닌 것도 포함할 수 잇다. 예를 들어 ‘A-B’ 도 심볼이 될 수 있다 하지만, ‘A – B’ 는 빼기 연산으로 처리되므로 주의해야 한다.
4.1.7 The Location Counter
링커에서 사용되는 특수한 변수로 점 ‘.’ 이 있다. 이 값은 항상 출력하는 위치정보를 가진다. ‘.’ 은 항상 출력 섹션의 위치를 참조 한다. 그러므로 ‘.’ 은 반드시 SECTIONS 명령 안에서 사용되야 한다. ‘.’ 심볼은 정규식 어디서든 사용할 수 있다. 대입 연산에서 ‘.’ 을 사용할때는 항상 side effect 를 고려해야 한다. ‘.’ 에 대입하는 것은 location counter 를 이동 시키는 것을 의미한다. 이것은 출력 섹션에 hole 을 만드는 용도로 사용된다. location counter 는 절대 뒤로 이동해서는 안된다.
SECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0x1234;
}
이 예제는 file1 을 출력 섹션의 처음에 위치시키고 1000 byte 의 간격을 둔다. 그리고 file2 가 나타나고 이 후에도 1000 byte 의 간격을 둔다. 바로 이어서 file3 가 적재된다. 마지막에 ‘= 0x1234’ 는 간격들에 쓰여질 데이터를 지정한다.
4.1.8 Operators
링커는 표준 C 에서 제공되는 수식 연산자를 인식하며, 다음표를 따른다.
| Precedence (highest first) | Associativity | Operators |
| 1 | left | - ~ ! (prefix operator) |
| 2 | left | * / % |
| 3 | left | + - |
| 4 | left | >> << |
| 5 | left | == != > < <= >= |
| 6 | left | & |
| 7 | left | | |
| 8 | left | && |
| 9 | left | || |
| 10 | right | ? : |
| 11 | right | &= += -= *= /= |
4.1.9 Evaulation
링커는 “lazy evaluation” 을 수식에서 사용한다. 즉 필요할 때만 실제 연산을 하는 것이다. 링커는 값의 시작 주소와 메모리 영역의 길이를 링킹 하기 위해 필요로 한다. 이 값들은 가능한한 연산을 하려고 한다. 하지만 다른 값들(심볼값)은 스토리지가 할당되기 전까지는 모르는 상태로 있거나 필요로 하지 않는다. 이러한 값들은 출력 섹션의 크기를 계산해야 하는 경우등과 같은 경우가 될때야 계산되는 것이다.
4.1.10 Assignment: Defining Symbols
전역 심볼을 생성하거나, 해당 심볼에 값(주소)를 대입 연산자를 이용해 대입할 수 있다.
| symbol = expression; symbol &= expression; symbol += expression; symbol -= expression; symbol *= expression; symbol /= expression; |
ld 표현에서는 다음 두 가지 특징으로 대입 연산자를 구분 한다.
-
대입 연산자는 수식의 처음에서 사용되어야 한다. ‘a=b+3’ 은 되지만, ‘a+b = 3’ 은 오류로 처리된다.
-
대입 연산의 끝에 항상 세미콜론을 붙여야 한다.
대입 연산자가 나타나는 경우는:
-
ld script 안에서..
-
SECTIONS 명령 안에서 독립적인 문장으로
-
SECTIONS 명령의 섹션 정의문의 구성 요소로
처음 두 경우는 절대 주소값을 사용한다는 점에서 그 효과는 같다. 마지막 경우는 특정 섹션에 상대적인 주소를 갖는 심볼이 된다.링커 수식이 평가되고 변수에 대입된다면, 절대 또는 상대 속성이 지정된다. 절대 수식 표현의 경우 출력 파일에 바로 나타날 수 있지만, 상대 주소인 경우 베이스 섹션을 기준으로 하는 오프셋으로 표현된다. 수식의 종류가 제어되는 것은 스크립트 파일 안에서의 위치에 의해서이다. 섹션 안에서 대입되는 심볼의 경우, 베이스 섹션을 기준으로 상대적으로 생성되는 것이며, 그 외 다른 곳에서 대입되는 값들은 절대 심볼로 구분된다. 하지만 섹션 안에서 정의되는 심볼이더라도 ABSOLUTE 키워드를 사용하면 절대값으로 처리된다. 예를 들어 .data 섹션안에서 절대 심볼로 생성하려 하는 경우에는:
SECTIONS {
...
.data :
{
(.data)
_edata = ABSOLUTE(.);
}
}
링커는 연산의 평가를 모든 텀들의 소스가 결정 될 때까지 미룬다. 예를 들어 섹션의 크기가 같은 경우, 할당이 모두 끝나 그 크기가 결정될 때 실행된다. dot ‘.’ location counter 가 포함된 경우, 할당하는 동안 연산이 처리된다. 연산 결과가 요청되는 상황이지만 값이 유효하지 않을 때에는 에러가 발생한다. 예를 들면 다음 스크립트 처럼:
SECTIONS {
text 9 + this_isnt_constant:
{
...
}
}
작성된 경우에는 “Non constant expression for initial address” 라는 에러를 발생시킨다. 어떤 경우에는 링커 스크립트에서 심볼을 정의할 때, 참조되는 경우이거나, 링크에 사용되는 오브젝트 안 어디에서도 정의되지 않은 경우에만 정의하도록 하고 있다 .예를 들어 전통적인 링커의 경우 ‘etext’ 를 정의하고 있지만, ANSI C 에서는 사용자가 etext 라는 함수명을 아무런 에러 없이 사용할 수 있도록 하고 있다. 이런 문제를 해결하기 위해 PROVIDE 가 제공된다. PROVIDE 는 etext 와 같이 참조는 되지만 정의되지 않은 것들을 위해서 사용한다. 문법은 PROVIDE(symbol = expression) 이다. 이러한 심볼들로는 etext, edata, end 가 있다. 프로그램 세그먼트를 가리키는 변수들이다.
extern etext;
extern edata;
extern end;
etext 는 텍스트 세그먼트의 종료점 이후 첫 주소를 의미한다. edata 는 초기화된 데이터 세그먼트 종료점 이후 첫 주소를 의미한다. end 는 초기화되지 않은 데이터 세그먼트 종료점 이후 첫 주소를 의미한다.(BSS segment 라고 많이 알고 있다) 하지만 이 값들은 표준은 아니다.
4.1.11 Arithmentic Functions
링크 스크립트에서 사용하기 위해 Command Language 는 많은 수의 내장 함수들을 제공하고 있다.
ABSOLUTE(exp)exp 수식의 절대값을 반환한다. 섹션안에 정의된 일반적인 변수들은 상대 주소를 표현한다. 이런 경우에 절대 주소를 표현하려면 이 함수를 사용하면 된다.
ADDR(section)section 이라는 이름의 섹션의 절대 주소값을 반환한다. 해당 섹션은 이전에 이미 정의되어 있어야 한다. 아래 예제 스크립트에서 symbol_1 과 symbol_2 는 같은 값을 갖게 된다.
SECTIONS {
.output1 :
{
start_of_output_1 = ABSOLUTE(.);
...
}
.output:
{
symbol_1 = ADDR(.output1);
symbol_2 = start_of_output_1;
}
}
LOADADDR(section)section 이라는 이름이 실제로 적재되는 절대 주소값을 반환한다. 이 함수는 ADDR 과 같은 역할을 하지만, AT 키워드가 사용된 경우에는 조금 다르게 동작한다.
ALIGN(exp)다음 exp의 경계에 맞도록 정렬된 현재 location counter(.) 의 값을 반환한다. exp 는 2의 멱수로 정의되어야 한다.(. + exp – 1) & ~(exp – 1)ALIGN 은 location counter 의 값을 바꾸지 않는다. 단지 연산을 할 뿐이다. 예를 들어, .data 섹션을 직전 섹션 뒤로부터 0x2000 바이트 경계로 정렬시키고, 입력 섹션의 다음 부터 0x8000 경계에 변수를 정의하려면:
SECTIONS {
.data ALIGN(0x2000) : {
(.data)
variable = ALIGN(0x8000);
}
}
와 같이 한다.처음 사용되는 ALIGN 은 섹션의 위치를 결정한다. 문법이 해당 섹션의 시작 위치를 결정하는 부분이기 때문에 그런 것이고, 두번째는 변수의 위치를 결정할 때 반영된다.
DEFINED(symbol)링커의 전역 심볼 테이블에 등록되었거나 정의되어 있는 심볼인 경우 1을 반환하고 그렇지 않으면 0을 반환한다. 이 함수를 이용해 변수의 기본 값을 정의 할 수 있다. 예를 들어 다음 command-file 의 일부는 어떻게 전역 심볼 begin 의 .text 섹션 안에서의 처음 위치를 결정하는지 보여준다. 만약 begin 이라는 심볼이 이미 정의되어 있다면 그 위치(값) 는 그대로 유지될 것이다.
SECTIONS {
.text : {
begin = DEFINED(begin) ? begin : . ;
...
}
}
NEXT(exp) exp 를 곱에 해당하는 할당되지 않은 다음 위치의 주소값을 반환한다. 이 함수는 ALIGN(exp) 와 깊은 관계가 있다. MEMORY 명령으로 출력 파일에서 연결되지 않은 메모리 공간을 정의하지 않는한, NEXT 와 ALIGN 함수는 똑 같은 일을 한다.
SIZEOF(section)section 이라는 이름의 이미 할당된 섹션의 크기를 바이트 단위의 수로 반환한다. 다음 예제에서 symbol_1 과 symbol_2 는 같은 값이 할당된다.
SECTIONS {
...
.output {
.start = .;
...
.end = .;
}
symbol_1 = .end - .start;
symbol_2 = SIZEOF(.output);
...
}
SIZEOF_HEADERS sizeof_headers 출력 파일의 헤더 크기를 바이트 단위의 값으로 반환한다. paging 기능을 사용하는 경우 독자가 원한다면 이 값을 첫 섹션의 시작 위치로 사용할 수 있다.
MAX(exp1, exp2) 두 수식 중 큰 값을 반환한다.
MIN(exp1, exp2) 두 수식 중 작은 값을 반환한다.
4.1.12 Semocolons
세미콜론은 다음 위치에서 필요로 한다. 그 외 다른 위치에서는 단순히 미적인 이유(관례적인 이유)로 등장 하며 무시된다.Assignment 대입 수식에서는 반드시 대입 수식의 마지막에 나타나야 한다.PHDRS PHDRS 문장의 끝에 반드시 나타나야 한다.
4.1.13 Memory layout
링커는 기본적으로 모든 사용 가능한 메모리 공간에 대한 할당을 허용한다. 독자는 이 설정을 MEMORY 명령으로 수정할 수 있다. MEMORY 명령은 타겟의 메모리 블록의 크기와 위치를 기술 할 수 있다. 이 명령을 주의 깊게 사용하여 링커에 의해 사용될 수 있는 공간과 피해야 하는 공간을 기술할 수 있다. 링커는 사용가능한 영역들을 맞추기 위해 섹션들을 서로 섞지는 않지만, 요구한 섹션들을 정확한 영역들로 이동시킨다. 만약 그 영역이 가득 차면 에러를 발생한다.명령 파일에서는 한 번의 MEMORY 명령을 포함할 수 있다. 하지만, 독자는 많은 메모리 블록을 원하는 만큼 정의할 수 있다. 문법은:
MEMORY
{
name (attr) : ORIGIN = origin, LENGTH = len
...
}
name 영역을 참조하기 위해 링커가 내부적으로 사용하는 이름이다. 어떠한 심볼 이름이든 될 수 있다. 영역 이름은 분리된 name space 에 저장되며, 심볼, 파일 이름, 섹션 이름과 충돌하지 않는다. 여러 영역을 지정하기 위해 구분되는 이름을 사용하면 된다.(attr) 링커 스크립트에 나타나지 않는 섹션의 위치가 될 특별한 메모리의 속성을 지정하기 위해 사용되는 추가적인 기능이다. 유효한 속성 목록은 ALIRWX 로 구성된다. 만약 속성 목록을 쓰지 않는다면, 가로를 쓰지 않아도 된다.
| ‘Letter’ | Section 속성 |
| ‘R’ | 읽기 전용 섹션 |
| ‘W’ | 읽고 쓸 수 있는 섹션 |
| ‘X’ | 실행 가능한 코드를 포함하고 있는 섹션 |
| ‘A’ | 할당된 섹션 |
| ‘I’ | 초기화된 섹션 |
| ‘L’ | I 와 같은 역할을 한다. |
| ‘!’ | 다음에 나타나는 속성의 반대를 의미한다. |
origin 물리 메모리 상에서 해당 영역의 시작 위치를 정의한다. 메모리 할당되기 전에, 상수로 반드시 평가되어야 한다. 키워드는 ORIGIN 으로 짤게하면 org 또는 o 가 된다. 그러나 ORG 와는 다른 것이다.len 바이트 단위의 영역 크기. LENGTH 라는 키워드로 len 또는 l 로 축약해서 사용할 수 있다.예를 들어, 두 영역의 할당 가능한 메모리를 지정하는 것을 – 하나는 0 부터 256 킬로바이트이고 다른 하나는 0x40000000 부터 4 메가바이트 까지 – 보자. rom 메모리 영역은 실행 가능한 코드를 읽기 전용으로 가지고 있는 영역이고 ram 영역은 그와 다른다.
MEMORY {
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
mem 이라고 이름 지어진 메모리 영역을 가지고 있다면, SECTIONS 영역 안에서 그것의 출력 섹션을 ‘>mem’ 을 이용해 직접 지정할 수 있다. 만약 합쳐진 섹션이 너무 크다면 링커는 에러를 낼 것이다.
4.1.14 Specifying output section
SECTIONS 명령은 입력 섹션이 출력 섹션의 어디에 위치해야 하는지, 출력 파일에서 그 순서는 어떻게 되는지, 어떤 출력 센션들이 할당 되는지를 제어할 수 있다.script file 에서 SECTIONS 명령을 한 번 사용할 수 있지만, 그 안에서 원하는 만큼의 문장들을 쓸 수 있다. SECTIONS 명령 안의 문장들은 다음 세가지 중 하나를 수행할 수 있다.
-
진입정을 설정한다
-
심볼에 값을 대입한다.
-
명명된 출력 섹션의 위치와 그 섹션에 어떤 입력 섹션들이 들어가는지 기술한다.
진입점과 심볼정의를 SECTIONS 명령 밖에서도 쓸 수 있다. 스크립트의 가독성을 높이기 위해서 여기에도 허용된 것이다. 그래서 심볼들과 진입정의 정의는 출력 파일의 레이아웃 어디에서든지 의미가 있는 곳에서 나타날 수 있다. 만약 SECTIONS 명령을 사용하지 않는다면, 링커는 각각의 입력 섹션의 이름과 나타나는 순서와 일치하도록 출력 파일을 만들어 낼 것이다. 예를들어 만약 모든 입력 섹션이 첫 번째 파일안에 있다면, 출력 파일에서도 그 순서가 유지될 것이다.
4.1.15 Section Definitions
SECTIONS 명령 안에서 가장 자주 사용되는 문장은 섹션 정의이다. 섹션 정의는 출력 섹션의 속성을 정의한다. 속성에는 위치, 정렬, 내용, 채우는 패턴, 그리고 대상 메모리 영역들이 있다. 이 속성들은 대부분 선택적인 것들로, 가장 간단한 섹션 정의 모양은
SECTIONS {
...
secname : {
contents
}
...
}
가 된다.secname 은 출력 섹션의 이름이고, contents 는 그 섹션 안에 들어가는 내용들을 정의한다. 예를 들어 입력 파일의 목록 또는 입력 파일들의 섹션들의 목록이 될 수 있다. secname 은 공백 문자로 둘러 싸여 있어야 한다. 그 이외의 다른 공백 문자들은 있어도 되고 없어도 된다 하지만, 콜론(:) 과 중가로({}) 는 반드시 있어야 한다.secname 은 출력 파일 형식의 제약 조건과 일치해야 한다. a.out 와 같이 제한된 개수의 섹션들만 지원하는 형식에서 그 이름은 만드시 그 포멧에 의해서 지원되는 이름들 중 하나이어야 한다. (a.out 에서는 .text, .data, .bss 만을 섹션 이름으로 허용하고 있다.) 만약 출력 형식의 제약 조건에서 개수만 제한하고 이름에 대한 제한이 없다면, 이름들은 반드시 따옴표로 묶인 숫자 문자열로 제공되어야 한다. 섹션의 이름은 어떠한 문자열의 순차 집합으로 구성될 수 있지만, ld 의 표준 심볼 이름 문법에 맞지 않는 경우라면 따옴표로 묶여야 한다. ‘/DISCARD/’ 와 같은 특별한 섹션 이름은 입력 섹션을 제거한다. ‘/DISCARD/’ 섹션으로 대입되는 섹션들은 최종 링크 결과물에서는 나타나지 않을 것이다. 링커는 내용이 없는 빈 섹션은 출력 섹션에 만들어 내지 않는다. 이 것은 존재하거나 존재 하지 않을 수 있는 입력 센션을 처리를 좀 더 편하게 하기 위한 것이다. 예를 들어
에서는 ‘.foo’ 섹션이 입력 파일에 적어도 하나의 .foo 섹션이 존재하는 경우에만 출력 파일에 생성될 것이다.
4.1.16 Section Placement
섹션 정의에서, 출력 섹션의 내용을 특정 입력 파일들의 목록이나 입력 파일 섹션들, 또는 두 방식을 혼합하여 지정할 수 있다. 또한, 임의의 데이터를 섹션에 위치 시키거나, 섹션의 시작을 기준으로 상대적인 심볼을 정의할 수 있다.섹션 내용을 정의할 때 다음과 같은 문장들을 포함 할 수 있다. 얼마든지 하나의 섹션 안에 원하는 만큼 포함 시킬 수 있고, 공백 문자를 이용해 구분할 수 있다.filename현재 출력 섹션에 들어간 입력 파일의 이름을 지정한다. 해당 입력 파일 안의 모든 섹션이 이 섹션 안에 위치하게 된다. 만약 다른 섹션에서 같은 파일의 섹션들이 명시적으로 지정되어 사용된 적이 있다면, 이 경우에는 사용된 섹션을 제외한 섹션들이 포함될 것이다.파일 이름으로 파일 목록을 정의하고 싶다면: .data : { afile.o bfile.o cfile.o }이 예제는 각 파일 이름이 문장으로 분리되어 있기 때문에, 여러 문장이 하나의 섹션 정의에 포함될 수 있다는 것도 보여주고 있다.filename(section) filename(section, section, …) filename(section section …)입력 파일에서 여러 섹션을 지정하여 현재 출력 섹션에 넣을 수 있다. 가로 안에서 입력 파일의 섹션 목록을 지정하려면 공백 문자로 구분하면 된다.(section) (section, section, …) (section section …)링크 제어 스크립트에서 특정 입력 파일을 지정하는 것 대신에, 관련된 모든 파일을 ld 명령어 라인에서 ‘*’ 를 사용하는 것으로 대신할 수 있다. 물론 이 경우에도 파일 이름을 지정한 경우와 같이 가로를 이용해 지정할 섹션 목록을 나열할 수 있다.만약 이미 이름을 이용해 지정한 파일이 있는 경우에는 ‘*’ 는 자동으로 그 이외의 출력 섹션에서 사용되지 않은 파일들을 지칭하게 된다. 예를 들어, Oasys 파일의 1 부터 4번 섹션을 a.out 파일의 .text 섹션과 .data 섹션의 13번 14번에 복사하려면:
SECTIONS {
.text : {
(“1” “2” “3” “4”)
}
.data : {
(“13” “14”)
}
}
‘[ section … ]’ 은 섹션 이름을 지칭하는 또 다른 방법으로 사용되기도 했으나, VMS 같은 특정 운영체제에서 파일 이름에 [] 문자를 사용할 수 있었기 때문에 이 표기법은 더 이상 사용되지 않는다.filename (COMMON) (COMMON) 이 표기 법으로 출력 파일에서 초기화되지 않은 데이터들의 위치를 지정할 수 있다. * (COMMON) 은 모든 입력 파일에 있는 초기화 되지 않은 공간 전체를 참조한다.(아직 할당되지 않았다); filename(COMMON) 은 해당 파일의 초기화 되지 않은 데이터를 지칭한다. 두 경우 모두 입력 파일의 섹션들을 위치시키기 위한 일반 적인 메커니즘의 특별한 경우에 속한다. ld 는 입력 파일의 형식과 관계 없이, 초기화 되지 않은 데이터를 COMMON 이라는 입력 파일의 섹션안에 위치한다고 강제로 여긴다.특정 파일 또는 섹션 이름을 위치 시키고 싶은 경우에는 와일드 카드 패턴을 사용할 수 도 있다. 링커는 와일드 카드를 유닉스 쉘이 처리하는 것과 같은 식으로 처리한다. ‘*’ 는 나타나는 횟수에 제한 없이 모든 문자에 대해 일치하는 패턴을 의미하고, ‘?’ 는 하나의 모든 문자에 대해 일치하는 패턴을 의미한다. ‘[chars]’ 순차 문자열은 chars 와 일치하는 모든 문장을 의미한다. ‘-‘ 문자는 문자 값의 범위를 지정하는 용도로 ‘[a-z]’ 는 소문자와 일치하는 패턴을 의미한다. ‘\’ (역슬래시) 문자는 뒤이어 오는 문자를 인용한다.와일드 카드로 매치되는 파일 이름에서 ‘/’ 는 유닉스상에서 디렉토리 이름을 구분하는 용도로 사용되는 문자이기 때문에 제외된다. 하지만 ‘*’ 는 모든 파일 이름과 매치된다. 그러므로 섹션 이름을 정의할 때 ‘*’ 는 ‘/’ 도 매치 시킨다.와일드 카드는 명령 라인에서 지정된 파일들에 대해서만 매치 시킨다. 링커는 와일드 카드를 위해 디렉토리 검색을 하지 않는다. 하지만, 와일드카드가 없는 간단한 파일 이름을 명령 라인에서 정의하지 않은 상태로 링커 스크립트 안에서 지정하면, 링커는 명령 라인에 파일이 나타난 것으로 인식해서 파일을 열려고 한다.다음 예제에서, 명령 스크립트는 세개의 연속된 섹션으로 출력 파일을 정렬시킨다. 각 섹션은 ‘.text’ ‘.data’ ‘.bss’ 라는 이름을 갖는 것으로 모든 입력 파일에서 찾아 정렬시킨다.
SECTIONS {
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) *(COMMON) }
}
다음 예제는 all.o 파일에서 섹션을 모두 읽어 0x10000 으로 시작하는 output 라는 출력 섹션에 위치 시킨다. foo.o 에 있는 .input1 섹션의 모든 것은 같은 출력 섹션에 바로 이어서 나온다. foo.o 에 있는 .input2 섹션의 모든 것은 outputb 라는 출력 섹션으로 출력된다. outputb 는 foo1.o 의 .input1 섹션 바로 다음에 나온다. .input1 과 .input2 에 남은 모든 섹션은 outputc 출력 섹션에 나타난다.
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
(.input1)
(.input2)
}
}
이번 예제는 와일드 카드 패턴이 어떻게 파일들을 나누는지 보여준다. 모든 .text 섹션은 .text 에 위치하고, .bss 섹션은 .bss 섹션으로, 대문자로 시작하는 모든 .data 섹션은 .DATA 로, 그 이외 다른 파일들에 있는 .data 섹션은 .data 섹션으로 위치된다.
SECTIONS {
.text : { *(.text) }
.
DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
4.1.17. Section Data Expressions
지금 까지는 입력 파일의 데이터들을 출력 파일로 정렬하는 문장들을 다루었다. 하지만, 링크 명령 스크립트를 이용해서, 직접 데이터를 출력 섹션에 위치 시킬 수 있다.