3.1. GCC 인라인 어셈블러

아주 유명한 GNU C/C++ 컴파일러인 GCC 이다. 이 컴파일러는 GNU 프로젝트의 핵심이 되는 32비트 컴파일러이다. 매우, 매우(!) 좋은 코드를 생산하며, x86 아키텍쳐의 프로세서 또한 매우 잘 지원한다. 이 컴파일러에는 C 프로그램 코드 중간에 인라인 어셈블리를 삽입할 수 있는 기능을 가지고 있는데, 이를테면, 레지스터 할당과 같은 것을 gcc 가 스스로 하게 하거나, 또는 프로그래머가 원하는 대로 조정해서, 원하는 레지스터를 할당하게 할 수도 있다. 게다가, gcc 는 거의 대부분의 플랫폼에서 사용가능하다. 리눅스, *BSD, VSTa, OS/2, *DOS, Win* 등과 같이 광범위한 운영체제에서 사용 가능하다.

3.1.1. GCC 를 어디서 구할 것인가?

가장 주된 사이트는 GNU 의 FTP 사이트 ( ftp://prep.ai.mit.edu/pub/gnu/gcc/) 이다. 이 사이트는 GNU 프로젝트의 산물인 많은 자유 소프트웨어들이 컴파일된 형태 혹은 소스의 형태로 발표되는 곳이다. 리눅스에 맞춰서 설정 및 컴파일된 버젼들은 ftp://metalab.unc.edu/pub/Linux/GCC/ 에서 찾을 수 있다. 또한, 세계 각처에 FTP 미러 사이트들이 있다. 원한다면, CD-ROM 으로도 구할 수 있다.

GCC 의 개발은 일전에 GCC2.8 과 EGCS 패키지의 두개의 가지로 나누어졌었다. 그러나 최근에 와서 이 두 패키지는 통합되어서 개발 및 배포되고 있다. 현재 GCC 의 웹페이지는 http://gcc.gnu.org 이다.

여러분이 즐겨 사용하는 운영체제를 타겟으로 만들어진 소스나 바이너리 또한 여러분이 자주 가는 여러분 운영체제의 FTP 사이트들에서 찾을 수 있을 것이다.

GCC 의 DOS 버젼은 DJGPP 라고 불리우는데, http://www.delorie.com/djgpp/ 에서 구할 수 있다.

또한 Win32 용의 GCC 포트도 있다 : cygwin ( http://sourceware.cygnus.com/cygwin/) 과 mingw (http://www.mingw.org) 가 그것이다.

심지어 OS/2 용의 GCC 포트도 있다. 이것은 EMX 라고 불리운다. 구할 수 있는 URL 은 다음과 같다. ftp://ftp-os2.cdrom.com/pub/os2/emx09c/. 여기서 구할 수 있는 EMX 는 도스에서도 작동하며, 패키지에는 유닉스 에뮬레이션을 위한 목적은 라이브러리 루틴도 포함되어 있다. 앞의 사이트에서 여기저기를 둘러보고 찾아 보라.

There is also an OS/2 port of GCC called EMX; it works under DOS too, and includes lots of unix-emulation library routines.

3.1.2. GCC 인라인 어셈블러에 관한 문서를 어디서 찾을 수 있는가

GCC 문서는 TeXinfo 포멧으로 문서파일들을 포함한다. 여러분은 그 문서들은 TeX 을 이용하여 컴파일해 출력해 볼 수도 있고, .info 파일로 변환해서 emacs 에서 브라우징 하면서 읽을수도 있다. 또는, .html 파일로 변환하여 웹 브라우저에서 읽을 수도 있고... 기타 여러분들이 좋아하는 포멧으로 변환할 수 있다. .info 파일은 GCC를 제대로 설치했다면 함께 설치되었을 것이다.

여러분이 정보를 얻기 위해서 찾아야 할 섹션은 C Extensions::Extended Asm:: 섹션이다.

또한, Invoking GCC::Submodel Options::i386 Options:: 섹션에 있는 내용도 상당히 유용할 것이다. 특히나 이 섹션을 자세히 살펴보면, i386 계열의 레지스터를 위해 규정된 이름에 대한 정보를 얻을 수 있는데 : abcdSDB 가 각각 %eax, %ebx, %ecx, %edx, %esi, %edi, %ebp 과 하나씩 일치하게 된다. (단 %esp 에 해당하는 것은 없음),

웹의 DJGPP 게임즈 리소스 페이지에 어셈블리에 관한 내용이 매우 많이 있었는데... 지금은 서버가 죽어 있는것 같다. 그러나, 자료는 DJGPP 사이트 에 복구되어 있다. 이 사이트에는 다른 유용한 정보들도 많이 존재한다 : http://www.delorie.com/djgpp/doc/brennan/, 또한, 정말 유용한 자료인 DJGPP Quick ASM Programming Guide 도 찾아볼 수 있다.

GCC 는 어셈블리 소스를 어셈블 하기 위해서 GAS 에 의존하며, GAS 의 문법을 따른다(아래를 보라:) 인라인 어셈블리를 쓸 때, 퍼센트 문자는 따옴표로 둘러싸야 한다는 것을 명심하라. 퍼센트 문자는 GAS 로 넘겨지게 된다. 자세한 내용은 아래의 GAS 에 관련된 섹션을 보라.

그리고, 정말 유용한 예제들의 집합체인 linux/include/asm-i386/ 디렉토리 아래를 살펴보라. 또한, 리눅스 커널의 서브디렉토리들도 살펴보도록 하라. 유용한 예제들을 정말로 많이 얻을 수 있을 것이다.

3.1.3. GCC 를 이용하여 인라인 어셈블리 코드를 생성하기

커널 헤더들에서 찾아볼 수 있는 어셈블리 루틴들은 extern inline 함수들 내부에 들어가 있기 때문에, (만약 여러분이 여러분이 만든 헤더파일을 가지고 있으며, 여러분의 어셈블리 코드를 리눅스 커널의 그것처럼 명료하게 작성하고 싶다면 여러분도 그처럼 하면 된다) 이들 루틴들이 사용가능하게 하기 위해서 GCC 는 반드시 -O 플래그와 함께 실행되어야 한다. (또는 -O2, -O3 등과 같은 옵션과 함께 실행되어야 한다.) 그렇게 하지 않으면, 여러분의 코드는 컴파일될런지는 몰라도, 제대로 링크되지는 않을 것이다. 왜냐하면, 프로그램이 링크될 때, 링커는 인라인이 아닌 extern 함수들을 여러분의 프로그램이 링크될 라이브러리에서 찾기 때문이다. 또 다른 방법은 루틴들의 fallback 버젼이 존재하는 라이브러리와 링크시키는 것이다.

Because assembly routines from the kernel headers (and most likely your own headers, if you try making your assembly programming as clean as it is in the linux kernel) are embedded in extern inline functions, GCC must be invoked with the -O flag (or -O2, -O3, etc), for these routines to be available. If not, your code may compile, but not link properly, since it will be looking for non-inlined extern functions in the libraries against which your program is being linked! Another way is to link against libraries that include fallback versions of the routines.

인라인 어셈블리가 사용하기 싫다면, -fno-asm 플래그를 주면 된다. 그렇게 하면, 컴파일러는 확장 문법인 인라인 어셈블리가 발견되었을 경우, 종료하게 된다. 그렇지 않으면, 링커가 인식하지 못하는 외부함수인 asm() 함수에 대한 호출을 생성하게 되어버린다. (번역이 좀 이상하네요... -_-a) 그 반대의 플래그는 -fasm 옵션으로써, asm 키워드에 대한 취급방법을 복구한다(?)

which will have the compiler die when using extended inline asm syntax, or else generate calls to an external function named asm() that the linker can't resolve. To counter such flag, -fasm restores treatment of the asm keyword.

보다 일반적인 것으로써, x86 플랫폼에서 실행되는 gcc 에 매우 유용한 플래그가 있는데, 바로

gcc -O2 -fomit-frame-pointer -W -Wall

이다. -O2 는 일반적으로, 쓸만한 최적화 레벨이다. 그 외의 최적화 레벨은 컴파일하는데 시간이 더 오래 글리고, 더 큰 코드를 생산한다. 그럼에도 불구하고, 생성된 코드의 수행성능은 약간 더 빨라질 뿐이다. 과도한 최적화 옵션을 주어서 효과적인 것은 매우 타이트한 루프가 있을때 뿐이다. 여러분이 정말, 강력한(!) 최적화 컴파일을 필요로 하는 경우에는 -O6 의 레벨까지 사용하는 것은 고려해 봄직하다.

-fomit-frame-pointer 옵션은 쓸데없는 frame포인터(역자주 : stack 의 내용을 복구하는 것과 관련되어서 컴파일러 내부적으로 생성하는 포인터입니다.) 를 유지하는 코드를 생성하지 않도록 한다. 그렇게 하면 생성된 어셈블리 코드는 보다 작고, 빠른 것이 되며, 더 나은 최적화를 위한 레지스터들의 확보를 가능하게 해 준다. 단, 디버깅 툴 (gdb 와 같은..) 을 사용한 디버깅이 불가능해진다.

-W -Wall 옵션은 유용한 경고메세지들을 발생시키도록 한다. 경고메세지를 통해 여러분은 혹시 발생할지도 모를 어리석은 에러들을 미리 체크할 수 있게 한다.

여러분은 일련의 프로세서 종속적인 최적화 옵션을 줄 수 있는데, 예를 들면, -m486 등과 같은 옵션이 그것이다. 그러한 CPU 특정의 옵션을 줌으로써, GCC 는 여러분이 타겟으로 삼는 프로세서에 보다 더 잘 맞고, 최적화된 코드를 만들어 줄 것이다. 또한, 최근의 GCC 는 -mpentium 과 같은 플래그도 가지게 되었다. (GCC 2.7.x 또는 그 이하의 버젼에서는 지원하지 않는다.) 또한 PGCC, 소위 펜티엄 GCC 라는 것은 더 많은 옵션들을 가지고 있다. 구할 수 있는 장소는 다음과 같다 : http://goof.com/pcg/ 이처럼 특정 프로세서에 최적화 시키는 플래그들은 리눅스의 커널을 컴파일 할 때 볼 수 있을 것이다. 여러분의 GCC 에 딸려온 TeXInfo 문서를 확인하면 보다 많은 정보를 얻을 수 있다.

-m386 옵션은 코드의 크기를 최적화시킬 수 있도록 해준다. 그것이 중요한 이유는 메모리 사용율이 매우 높은 컴퓨터들에서 속도에 최적화되었지만, 그 최적화로 인해 크기가 매우 큰 프로그램을 실행시킨다면, 십중팔구 스와핑이 발생하게 되며, 그로 인해 원래의 목적인 빠른 속도의 효과에 반하는 일이 발생한다. 이럴 경우에는, C 를 쓰는 것 보다, 코드의 분할(?)을 구현한 언어 (functional language 혹은 FORTH 라고 불리우는 언어를 사용해서, 바이트코드나 워드코드에 기초한 구현을 하도록 하라.)

In such settings, it might be useful to stop using C, and use instead a language that favors code factorization, such as a functional language and/or FORTH, and use a bytecode- or wordcode- based implementation.

여러분은 최적화 옵션을 파일별로 다 다르게 지정할 수 있다. 그래서, 매우 좋은 성능이 요구되는 루틴을 담고 있는 파일에는 최고 레벨의 속도를 낼 수 있도록 최적화를 수행하고, 다른 파일에는 생성되는 코드의 크기를 작게 하는 방향으로 최적화할 수도 있다는 것을 유념하라.

좀 더 나은 최적화를 시키려면 -mregparm=2 옵션을 사용하거나, 해당하는 함수 특성을 사용하는것이 도움이 될 것이다. 그러나, 다른 코드(libc등과 같은)의 링크시에 몇가지 문제가 발생할 수 있다는 사실을 명심하라(역자주 : 번역이 정말-_- 매끄럽지 못합니다.) To optimize even more, option -mregparm=2 and/or corresponding function attribute might help, but might pose lots of problems when linking to foreign code, including libc.

외부의 함수들을 정확하게 선언해 줌으로써, 올바른 순서에 따른 호출이 발생하도록 조정해 주거나, 외부 라이브러리들을 여러분이 만든 프로그램과 같은 레지스터를 사용하는 호출 컨벤션을 사용하도록 재컴파일 해 줌으로써, 문제를 극복할 수 있기도 하다.

There are ways to correctly declare foreign functions so the right call sequences be generated, or you might want to recompile the foreign libraries to use the same register-based calling convention...

/usr/lib/gcc-lib/i486-linux/2.7.2.3/specs 을 편집해서 여러분의 make 에 위에서 설명한 플래그들이 디폴트가 되도록 할 수도 있다.(그러나 그 경우, -W -Wall 옵션은 덧붙이지 않는 것이 나은 경우도 있다.) 여러분의 시스템에서 GCC specs 파일의 정확한 위치는 gcc -v 를 함으로써 알 수 있다.

3.1.4. 매크로의 지원

GCC 는 여러분이 여러분의 인라인 어셈블리 코드 내에 레지스터 규약(? - constraints)을 명시하는 것을 가능하게 합니다. 그럼으로써, 최적화 할 때 컴파일러가 그 사실을 참고하여 인라인 어셈블리 코드가 패턴들의 집합으로 생성되도록 합니다. (-_-; 너무 번역을 못하는 거 같군여)

GCC allows (and requires) you to specify register constraints in your inline assembly code, so the optimizer always know about it; thus, inline assembly code is really made of patterns, not forcibly exact code.

따라서 여러분은 여러분의 어셈블리를 CPP 매크로 라든지, 인라인의 C 함수들 내부에 포함 시킬 수 있습니다. 그렇게 하면, 누구든지, 그 어셈블리 코드를 평범한 C 함수나 매크로처럼 사용할 수 있게 됩니다. 인라인 함수들은 매크로와 매우 비슷하게 보이지만, 때로는 더욱 명확하여 쓰기가 더 쉽다. 그러나, 매크로를 사용하든, 인라인 함수를 쓰던, 계속 중복된 코드를 쓰게 된다는 것을 명심하라. 따라서, 1: 과 같은 스타일의 로컬 레이블만이 그러한 어셈블리 코드에서 정의되고, 사용되어야 한다. 그러나, 매크로는 로컬 레이블로 정의되지 않은 레이블이 파라미터로 넘겨지는 것을 허용한다. (혹은, 여러분은 부가적인 메타 프로그래밍 기법을 사용할 수 있을 것이다.) 또한, 인라인 어셈블리 코드를 확장하게 되면, 그 코드 안의 잠재적인 버그 또한 나타날 수 있다. 그러므로, 그러한 인라인 어셈블리 코드에서 레지스터 사용방법을 이중으로 고찰하도록 하라.

Thus, you can make put your assembly into CPP macros, and inline C functions, so anyone can use it in as any C function/macro. Inline functions resemble macros very much, but are sometimes cleaner to use. Beware that in all those cases, code will be duplicated, so only local labels (of 1: style) should be defined in that asm code. However, a macro would allow the name for a non local defined label to be passed as a parameter (or else, you should use additional meta-programming methods). Also, note that propagating inline asm code will spread potential bugs in them; so watch out doubly for register constraints in such inline asm code.

마지막으로, C 언어 그 자체가 어셈블리 프로그래밍의 매우 좋은 추상화라는 것을 명심하고, C 를 사용함으로써, 어셈블리 프로그래밍시 발생할 수 있는 많은 난점들 극복하고, 보다 신뢰도 높은 프로그램을 여러분이 만들 수 있게 될 것이다.

Lastly, the C language itself may be considered as a good abstraction to assembly programming, which relieves you from most of the trouble of assembling.