컴퓨터를 만듭시다. 어때요~ 참 쉽죠? (22)
#22. 컴퓨터 세계의 번역가
지난 이야기에서 어셈블리어의 문법을 확정했다. 어셈블리어의 문법을 확정하면서 우리는 어셈블리어를 이용해 프로그래밍이 가능해졌다. 처음에 아무것도 없이 전선과 릴레이만 가지고 시작했던 이야기가 이제는 프로그래밍이 가능한 컴퓨터를 만드는데까지 발전했다. 물 흐르듯 흘러온 이야기지만 돌이켜보면 새삼 놀라울 따름이다. 어셈블리어로 프로그래밍을 하고 나면 그것을 다시 기계어로 바꿔줘야만 우리가 만든 컴퓨터에서 동작한다. 어셈블리어로 된 소스코드를 기계어로 바꿔주는 역할을 하는 프로그램이 어셈블러다. 최초의 어셈블러는 아마 기계어로 작성되었을 것이다. 그리고 그 이후의 어셈블러는 기계어로 작성된 어셈블러로 컴파일되었을 테고 계속 반복해서 커졌을 것이다. 요즘은 당연히 어셈블러도 C언어같은 고급 언어로 만들어진다.
자, 그럼 어셈블러를 만들어 보자.
어셈블러를 만든다고 하니까 정말 C언어나 자바 같은 프로그래밍 언어 코드가 마구 나오면서 코드를 설명하는 방식이 될것이라고 생각하는 분들도 계실 것이다. 절대 그러지는 않을 것이다. 지금까지 내가 cpu를 만들어오는 과정을 봐왔지 않은가? 나는 어디까지나 이야기를 하고 있는 것이지 무언가를 실제로 만들고 있진 않다.
어셈블리어 코드를 읽어서 이것을 기계어로 바꾸는 과정도 엄밀히 보면 컴파일러의 동작과 다르지 않다. 다만 고급언어보다 문법이 단순할 뿐이다. 그리고 어셈블리어에도 변수나 레이블등의 심볼 데이터가 존재하기 때문에 컴파일러 이론을 공부할 때 많이 나오는 심볼 테이블도 유지해 줘야 한다.
내가 이야기할 어셈블러가 하는 일은 아주 단순하다. 어셈블리어 코드를 한 줄씩 읽으면서 심볼 정보인 경우에는 심볼 테이블에 해당 정보를 입력한다. opcode인 경우에는 opcode를 기계어로 바꾸어 버퍼에 저장한다. opcode가 심볼을 참조하고 있다면 버퍼에서 해당 opcode의 오프셋을 심볼테이블에 기록한다. 파일의 끝까지 읽었다면 심볼 테이블 정보를 최종 완성하고 심볼 테이블에 기록된 버퍼 오프셋으로 찾아가 기계어를 완성한다. 그리고 말미에 어셈블리어 문법을 하나 더 추가할 것이다.
먼저 심볼 테이블의 구조를 설계하자. 간단하게 설계될것 같다.
심볼 테이블이니 당연히 심볼 이름이 있어야 하고, 해당 심볼이 대치되는 값 혹은 주소가 있다. 그리고 해당 심볼을 참조하는 버퍼 오프셋이 리스트로 들어가게 된다. 고급 언어에서도 마찬가지지만 심볼이라는 것은 결국 나중에 어떤 숫자로 바뀌게 된다. 심볼이 변수라면 주소로 바뀔 것이고 심볼이 상수라면 DEF로 선언된 값 자체로 대치될 것이다. 그렇기 때문에 심볼 테이블에는 해당 심볼이 대치될 숫자(값 혹은 주소)가 있어야 한다. 또 어느 opcode에서 심볼을 참조하고 있는지 알아야 그 위치에 찾아가서 심볼을 값으로 대치할 수 있기 때문에 참조하고 있는 위치도 심볼 테이블에서 가지고 있어야 한다.
DEF로 선언되는 상수의 경우 선언과 동시에 그 값이 바로 심볼 테이블에 등록된다. DEF로 선언된 심볼은 메모리 상에 별도의 영역을 가지고 있어서 바뀔 수 있는 것이 아니라 최초 선언된 값 자체가 계속 쓰이는 것이기 때문에 심볼 테이블에 바로 등록되어 사용할 수 있다. 하지만 BYTE, WORD로 선언되는 변수의 경우에는 상황이 조금 다르다. 변수는 메모리상에 정해진 크기 만큼 공간을 가지고 있어야 한다. 그래야 값을 나중에 바꿀 수 있기 때문이다. BYTE 변수인 경우에는 메모리에 1 바이트 크기만큼을 가지고 있어야하고 WORD 변수인 경우에는 메모리에 버스 폭 만큼, 저번 이야기에서 말한 cpu인 경우에는 4바이트 만큼의 메모리 영역을 가지고 있어야 한다. 어셈블러는 이 메모리 영역을 어디에 위치시켜야 할지에 대한 정책을 가지고 있어야 한다.
변수 영역을 위치시키는 몇 가지 방법에 대해 이야기해 보자면 우선 변수 선언부에 바로 위치시키는 방법이 있다. 이 방법을 사용하면 심볼 테이블에서 참조 위치 정보를 가지고 있는 리스트를 유지할 필요가 없어진다. 다만 심볼 테이블의 참조 위치 정보를 빼기 위해서는 변수 선언 이후에만 변수를 참조할 수 있다라는 문법적 제약을 하나더 만들어야 한다는 문제가 생긴다. 게다가 어셈블리어 코드 중간 중간에 변수 선언을 할 경우 기계어로 모두 변환되었을 때 코드 영역(기계어 영역)과 데이터 영역이 아주 난잡하게 섞여 버리는 문제가 생긴다.
두 번째 방법은 어셈블러가 데이터 영역을 한 곳에 모아 놓는 방법이 있다. 그 위치는 코드 영역의 앞 부분이 될 수도 있고 뒷 부분이 될 수도 있다. 나는 뒷 부분에 놓는 것을 좋아한다.
이렇게 코드 영역이 끝난 다음 변수들이 배정된 데이터 영역을 놓으면 어셈블리어 코드 중간 중간에 변수 선언을 해도 상관이 없고, 변수를 선언하기 전에 참조해도 상관이 없다. 다만 변수가 선언되어서 심볼 테이블에 등록되는 순간에는 변수가 실제로 위치할 메모리 주소를 알 수 없기 때문에 심볼의 주소 부분이 비어 있는 상태로 있게 되고 나중에 코드 영역의 번역이 끝난 다음에 데이터 영역 할당이 끝나고 나서야 심볼 테이블에 변수 심볼에 대한 주소 공간에 값이 등록된다. 기계어 번역이 다 끝나고 심볼 테이블이 완성되기 때문에 당연히 심볼 참조 위치에 대한 정보가 있어야 심볼 정보를 숫자로 정확히 바꿀 수 있다.
레이블 정보도 당연히 심볼 테이블에 등재되어야 한다. 레이블도 DEF를 처리할 때와 동일하게 처리하면 된다. 다르게 보면 레이블은 값이 현재 위치인 DEF라고 보면 된다. DEF는 명시적으로 값을 선언하지만 레이블은 그 값을 PC에서 받아오면 된다. 그래서 심볼 테이블에 등록될 때는 어셈블러 내부에서 opcode를 카운트하는 PC 레지스터 같은 카운터를 두고 있다가 레이블이 나올때 마다 이 값을 레이블에 대한 심볼 테이블에 등록시켜 줘야 한다.
선언부를 지나고 코드 영역이 나오면 opcode와 기계어가 대응된 코드표에 따라서 기계어로 번역을 해 나간다. 다만 심볼을 참조하고 있을 경우에는 해당 심볼을 심볼 테이블에서 찾아보고 심볼이 있을 경우 심볼의 참조 위치 목록에 해당 opcode의 PC 카운터 값을 넣는다. 나중에 심볼 테이블이 완성되고 나면 심볼 테이블의 참조 위치 목록을 찾아가서 실제 값을 넣어주기 위함이다.
여기서 해결해야 할 문제가 생긴다. 선언이 코드보다 늦게 해석되는 경우 코드를 해석하는 동안에는 심볼 테이블에 심볼 정보가 없다. 그러므로 심볼 테이블에 참조 위치를 등록할 수 없다. 어떻게 해결해야 좋을까. 그래서 대부분의 어셈블러는 소스코드를 두 번 해석한다. 책에서는 two-pass 어셈블러라고 써있기도 하다. 물론 한 번만 해석하게 만들 수도 있다. 그렇게 되면 아까 말한 것 처럼 코드보다 늦게 해석되는 심볼에 대해서는 참조 정보를 만들기가 꽤나 복잡해 진다. 물론 one-pass에서도 나중에 선언되는 심볼에 대한 참조 정보를 만들 수도 있지만 심볼 테이블에 임시 선언 심볼을 만들는 작업을 해야 하는 등 오히려 two-pass보다 더 복잡한 면이 많다.
two-pass는 용어 그대로 두 번 코드를 해석한다. 첫 번째 해석에서는 코드는 건너뛰고 심볼 테이블만 완성한다. 대신 opcode를 하나씩 건너뛸 때 마다 어셈블러 내부의 PC 카운터는 opcode 길이 만큼 증가시킨다. 그래서 첫 번째 해석이 다 끝나고 나면 완전한 심볼 테이블이 완성된다. PC 카운터를 opcode 만큼 증가시켰기 때문에 변수들이 배정될 데이터 영역 주소가 확정되기 때문이다.
두 번째 해석에서는 변수나 DEF 등 심볼 정보는 건너 뛰고 코드만 기계어로 번역한다. 기계어로 번역하면서 심볼을 사용한 부분이 있으면 즉각 심볼 테이블에서 주소 정보를 가지고 와서 대치한다. 만약 이때 심볼 테이블에 없는 심볼을 사용했다면 즉시 없는 심볼이 나타났다는 에러를 뿌리며 어셈블러가 작업을 중단한다.
마지막으로 어셈블리어 문법을 하나 더 추가하자. 추가할 문법은 EXTERN 문법이다. C언어의 extern 선언과 완전히 똑같은 동작을 한다. 대부분의 프로그램은 하나의 소스 파일로 이뤄져 있지 않다. 어셈블리어 소스 파일도 마찬가지다. 여러개의 파일이 서로 엮이면서 (Linking) 마지막에 최종적인 기계어 파일이 생성된다. 이렇게 해야만 그 편하다는 라이브러리의 구현도 가능해 진다.
EXTERN 심볼
위 문법으로 간단하게 사용될 수 있다. EXTERN으로 선언된 심볼은 심볼 테이블을 만드는 과정에서 심볼 테이블에 등록되지만 특별히 외부에서 선언된 심볼이라는 표시를 심볼 테이블에 해 둔다. 그리고 EXTERN 으로 선언된 심볼을 참조하는 opcode 쪽에는 임의로 00000 등의 값을 넣어 두고서 나중에 링커에 의해서 링크할 때 다른 파일에서 심볼의 값을 읽어서 넣어준다. 이 과정은 C언어에서도 완전히 동일하게 이뤄진다. 그래서 C 언어에서 extern 선언한 변수가 없거나 하는 문제가 발생하면 컴파일 시점이 아니라 링킹 시점에서 에러가 나는 것이다.
링커에 의해서 EXTERN 심볼이 값으로 대치될 때는 단순하게 다른 파일에 있는 주소 값이 그대로 와서는 안된다. 모든 파일의 기계어 코드 부분과 변수 데이터 부분이 결국에 하나로 합쳐져 실행 가능한 바이너리 파일이 만들어 지는 것이기 때문에 EXTERN 심볼을 가져올 때는 생성되는 기계어의 사이즈와 데이터 영역의 사이즈를 고려해서 적절한 처리를 해 줘야 한다. 이 부분에 대한 이야기는 나중에 링커에 대한 이야기를 할 때 다시 할 것이다.
아주 짧은 글에 약간은 날로 먹는 설명으로 어셈블러의 어셈블리어 컴파일 과정을 설명했다. 이 설명만으로 어셈블러를 만들어낸다면 당신은 천재! 그만큼 부족한 이야기다. 하지만 내가 하는 이 이야기의 목적이 어셈블러를 만드는 것에 있는 것이 아니라 하드웨어를 거쳐서 소프트웨어의 이야기를 진행하는데 지나가는 발전 방향에 대한 것이기 때문에 맛보기 혹은 훑어보기 식으로 가볍게 읽어 주길 바라는 마음에서 깊은 이야기는 하지 않았다. 나는 개인적으로 어셈블러에서 고급언어 컴파일러에 이르는 심도 깊은 이야기를 또 쓰고 싶은 바램이 있다. 과연 언제쯤이나 가능할런지는 모르겠지만 이 이야기를 쓰면서 한번 예행연습을 해 볼까 한다. 여러분들도 같이 동참하는 기분으로 읽어주시기 바란다.
이제 남은 이야기가 얼마 남지 않았다. 이 이야기를 처음 시작할 때는 언제쯤 끝날까 기약도 없이 시작했지만 이제 조금씩 끝이 보이기 시작하니 무언가 아쉽고 허접한 글을 검증도 없이 써 제껴서 혹시라도 해당 분야 전문가나 전공자 분의 눈살을 찌뿌리게 만들지는 않았나 소심해진다. 그래도 끝까지 쓸 것이다. 얼마 남지도 않았으니까.
P.S 이번엔 다른 툴로 그림을 그려 봤다. 화사하지 않은가? 아닌가? -_-;
첨부 | 파일 크기 |
---|---|
_22_01_symbol_table.png | 23.84 KB |
_22_02_not_align_data_area.png | 8.02 KB |
_22_03_align_data_area.png | 4.44 KB |
_22_04_one_pass.png | 60.3 KB |
댓글
언제나 잘 보고 있습니다^^
감사합니다.
___________________________
I like Small Linux.
___________________________
I like Small Linux.
그림이 더
그림이 더 좋아졌네요 :)
--------------------------
피할 수 있을때 즐겨라!
http://snowall.tistory.com
피할 수 있을때 즐겨라! http://melotopia.net/b
참고할 만한 사이트나 기타 서적들을 기술해 주면 좋겠습니다.
글 잘 읽고 있습니다.
앞으로도 좋은글 많이 써 주세요.
아... 제가 책이나
아... 제가 책이나 사이트를 참고하지 않고 썼거덩요..-_-
그냥 전부 제가 아는 내용을 바탕으로 쓴거라, 레퍼런스를 쓸게 없어요...
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
감사하게 잘 보고
감사하게 잘 보고 있습니다.
KINDLE에 담아두고 들고 다니면서 보고 싶지만 변경금지가 붙어있어서 그냥 참고 있습니다.
> 그냥 전부 ... 아는 내용을... 레퍼런스를 쓸게 없어요...
!!!
--
I think to myself...what a emerging world.
킨들에 넣어도
킨들에 넣어도 내용이 바뀌진 않으니
상관없습니다.
킨들에 넣으세요. (저작권자(me)가 방금 허락한 겁니다...ㅋㅋㅋ)
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라
감사합니다. :) 오늘
감사합니다. :)
오늘 밤 은밀히 작업에 들어가겠습니다. 제 킨들에 들어가는 첫 한글 문서가 되겠군요. 영광입니다. :)
--
I think to myself...what a emerging world.
잘보았습니다.....
좋은 자료 감사화비낟..
완전 생초보
댓글 달기