※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


Symbol and Symbol Tables 이 부분 내용을 아예 싸그리 고쳤다. 틀려서라기보다는 정리가 별로 잘 안되있는 것 같아서 처음부터 다시 썼다.


http://operatingsystems.tistory.com/entry/SP-Symbol-and-Symbol-Tables


위의 링크로 고쳐놨으니 다들 참고하시길 바란다.

'Correction' 카테고리의 다른 글

[Correction] Ensemble : Bagging, Random Forest and Boosting  (0) 2016.09.11
[Correction] Symbol and Symbol Tables  (0) 2014.04.02
[Correction] Procedure Call  (0) 2013.11.10
[Correction] NW-TCP-and-UDP  (0) 2013.09.17
[Correction] NW-IP-header  (0) 2013.08.21
시작하며  (0) 2013.08.21
Posted by 빛나유

댓글을 달아 주세요

※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


이번 포스팅은 이전에 얘기한데로 symbol과 symbol table에 대해서 조금 더 자세하게 들어가보겠다. 쓰다보면은 이 사람이 지금 symbol에 대해서 설명을 하고 있는지 linker에 대해서 설명을 하는지 혼동될 정도로 symbol과 linker는 긴밀한 연관 관계가 있다. 


symbol은 이전에 설명했듯이 하나의 object file에서 쓰이는 변수나 함수들을 의미한다. 이것 각각에 대한 정보를 한 곳에 모아둔 것이 symtab 즉 symbol table이다. 이렇게 아는 것 마냥 지금 이야기를 하는데... 공부를 하면서 가장 궁금했던 것이 바로 symbol에 대한 실체이다. symbol table이 symbol에 대한 정보를 모아둔 테이블이니까 어딘가에는 symbol 그 각각의 무언가가 있지 않을까나? 나는 그렇게 생각해서 symbol의 실체를 찾으려고 막 별 짓을 다했었다. 그런데 생각해보니 진짜 별거 아니더라. 


생각해보자. 하나의 C파일에서 int a = 1; 과 같이 global 변수를 선언했다고 하자. 이건 당연히 symbol로 분류되겠다. 이것에 대한 실체는 어디있는가? 너무 쉬운 답이다. 바로 data section에 있다. data section의 어느 주소 부분에서 4 byte만큼의 공간을 차지하고 있는 것이다. 그것이 바로 symbol의 실체이다. 


가령 또 다른 C 파일에서 void func(void); 와 같이 함수가 선언되어있다고 해보자. 이 함수 역시 symbol로 분류된다는 것에는 의심할 여지가 없다. 그렇다면 이것의 실체는? 이거 역시 너무 쉬운 질문이 된다. 당연히 text 영역이다. text 영역에 machine code로 존재할 것이며 그것을 사람이 보기 쉽게 바꿔놓으면 push %ebx, %esp 와 같은 assembly code가 되는 것이지.


위와 같이 symbol이 존재할 수 있다. symbol로 정의된 변수나 함수들의 정보를 한 곳에 모아둔 것이 바로 symtab이다. 그렇다면 symbol table은 각 symbol의 어떠한 정보를 보여주는지 알아보자.


/include/linux/elf.h


실제 linux 커널에서 위와 같이 정의되어 있다. 본능적으로? 알겠지만 32비트에서의 또는 64비트에서의 elf structure를 표현한 것이다. 각 symbol들의 저 정보가 sybtab에 표현된다. symtab에서 표현되는 정보를 다시 한번 알아보기 위해서 이전 포스팅에서 봤던 symtab 예시를 다시 한번 봐보자.



한 줄만 골라서 비교해보면 value, size, type, name, index 정보를 볼 수 있고 Bind 정보는 아마도 unsigned char st_info;에서 표현되고 있는 것 같다. 또한 위의 그림에서 Vis는 정확히는 모르겠지만 unsigned char st_other; 부분으로 추정된다. 이 부분은 아직 정의되지 않은 부분으로 그냥 0으로 초기화되어있는 부분이다. 아직 쓰임이 없는 부분이 아닐까 싶다.


이정도 했으면 symbol과 symbol table 그리고 section에 대해서 혼동되지 않을 정도로 자세하게 설명하지 않았나 싶다. 다음에 설명할 부분이 링커에 대한 부분과 많이 연관이 있다. 우선 링커가 하는 일을 다시 한번 짚어보자. 링커는 .o 파일의 relocatable/shared object file들을 한데 모아서 하나의 executable object file을 만드는 것이다. 우리가 보통 컴파일을 할 때는 gcc 명령어를 이용해서 preprocessing → compiling → assembling → linking 한꺼번에 패키지로 해버린다. 그래서 아마 아래와 같은 질문에 대답을 제대로 못 하는 경우가 생길 것 같다. 아래의 코드가 컴파일이 될까?



안 된다고 말하는 사람이 있을 수도 있다 왜냐하면 swap() 함수가 실제로 구현되어 있지 않기 때문에. 그런데 된다. 컴파일은 된다. 하지만 링크 하는 과정에서 에러가 발생한다. swap() 함수에 대한 적절한 definition을 찾지 못 하기 때문에 링크하는 과정에서 에러가 발생한다. 이 말인 즉 컴파일 할 때는 문제가 없다는 것이다.. 컴파일은 .s 파일을 만들어내는 과정인데, 이 때 컴파일러는 "swap() 함수가 있네... 뭐~ 나중에 다른 파일이랑 링크될 때 다른 파일 어딘가에는 선언되어있겠지 뭐~" 하고 넘겨버린다. 따라서 컴파일러는 위의 경우에 대해서 에러를 내면 안 된다. 나중에 링크하는 과정에서 다른 object file에서 선언되어 있을 확률이 있기 때문이다.


자 그러면 링커가 하는 역할을 다시 한번 조금 더 구체적으로 정의해보자. 링커는 각각의 object file에 있는 symbol들이 제대로 정의되어있는지를 확인해서 하나의 executable object file로 만들어 내는 것이다. 여기서 제대로 정의되어있는지 여부는 정의되어있는 것이 오로지 한 개 인지를 확인하는 것이다. 이것에 대한 링커만의 규칙이 아래와 같이 정의되어 있다.


Rule 1 : Multiple strong symbols are not allowed.

Rule 2 : Given a strong symbol and multiple weak symbols, choose the strong symbol.

Rule 3 : Given multiple weak symbols, choose any of weak symbols.


내가 공부하는 책에 쓰여있는 것을 그대로 적은 것이다. 위의 규칙들을 이해하기 위해서는 strong symbol과 weak symbol에 대한 이해가 선행되어야 한다. 그렇게 어려운 개념은 아니다. 


strong symbol은 함수나 initialized global variable이다.

weak symbol은 uninitialized global variable이다.


간단한 개념이다. 그렇다면 이제 위의 3개의 링커만의 규칙을 공부해보자.


Rule 1 : Multiple strong symbols are not allowed.

여러 개의 strong symbol은 허용되지 않는다. 가령 아래와 같은 예제를 의미한다.

편의상 한 파일에 적었다-_- 위의 경우는 링커에 의해 에러가 발생한다. main이라는 함수(strong symbol)이 두 번 선언되었기 때문이다. 당연히 foo1.c와 bar1.c를 컴파일 할 때는 문제가 발생하지 않는다. 그런데 컴파일 해서 생긴 foo1.o bar1.o를 링커로 연결할 때 문제가 생긴다는 거다. initialized variable로 인한 문제는 딱히 예시로 보여주지는 않겠다. 아무튼 initialized global variable이 두 개 이상 있으면 안 된다는 것이다.


Rule 2 : Given a strong symbol and multiple weak symbols, choose the strong symbol.

이건 더 쉽다. strong symbol 한 개와 weak symbol 여러 개가 있을 때는 strong symbol를 선택한다는 것이다. 아래와 같은 경우, foo1.c에 있는 int a = 1;를 선택한다.

여기서 둘 중에 하나를 선택하니 어쩌니... 헛깔려하는 사람이 있을 거 같아서 한번 집고 넘어가보자. foo1.c를 컴파일하면 foo1.o가 나오겠지? 그건 relocatable object file이겠지? 거기에 있는 symbol 중에서 int a가 있겠지? (int a = 1로 선언되어 있으니까 strong symbol) 마찬가지로 bar1.c를 컴파일한 bar1.o에도 int a가 있는데 uninitialized global variable이므로 weak symbol이다. 이 두 파일을 링커로 연결할 때 int a에 대한 선언은 foo1.o에 있는 걸로 할꺼냐? 아니면 bar1.o에 있는 걸로 할꺼냐? 이럴 경우에 대한 규칙이다. 이럴 경우 strong symbol인 foo1.o에 있는 걸 선택한다는 것이다.


Rule 3 : Given multiple weak symbols, choose any of weak symbols.

이것도 쉽다. weak symbol이 여러 개 있으면 아무거나 선택하라는 것이다.

아무거나 둘 중에 하나 선택하라는 것이다.


링커가 여러 개의 multiple-defined symbol를 만나면 처리하는 규칙에 대해서 알아봤다.

여기서 하나 더 알아봐야 하는 링커의 규칙이 있다. 보통 우리가 리눅스 환경에서 링커 명령어를 사용할 때 아래와 같이 사용할 수 있다.


ld -o hello main.o swap.o 


사실 위와 같이 실행을 하면 "cannot find entry symbol _start ....." 와 같은 에러가 날 것이다. _start 라는 함수는 hello라는 프로그램의 entry point를 정해주는 그런 역할을 하는 함수인데 그 함수가 정의되어있지 않기 때문에 위와 같은 에러가 난다. 해결법은 _start가 정의되어있는 object file을 찾아서 같이 링크해주면 된다. 가령 그 파일이 /usr/lib/abc.o라고 한다면 아래와 같이 사용하면 될 것이다.


ld -o hello main.o swap.o /usr/lib/abc.o


위의 명령어는 틀릴 수 있다는 것을 잊지 말아줬으면 한다. 단순히 설명을 하기 위해 쓴 예시일 뿐이다. 그래서 말하고 싶은 것은 순서이다. 링커는 object file을 나열할 때 object file의 순서에 따라서 에러가 날 수 있다. 이 부분은 개인적으로 공부해보길 바란다. 우선 나도 그렇게 제대로 알지 못 하고 중요도에 비해 양이 많은 것 같기도 하다.


다음 포스팅은 relocation에 대한 내용이 될 것이다. 참 이 부분 공부하기가 그렇게 쉽지 않을 줄은 알았는데 생각해보면 별로 어렵지도 않는 것 같기도 하면서 그렇게 또 쉬운 것 같지도 않고... 이건 무슨 쉬운 건지 어려운 건지 결정하는게 더 어렵다.


이게 어렵든 쉽든 나는 최선을 다해서 공부할 거다.

Posted by 빛나유

댓글을 달아 주세요

  1. 2014.10.07 11:16  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 빛나유 2014.11.03 11:20 신고  댓글주소  수정/삭제

      잘 보셨다니까 저도 좋네요ㅋㅋ
      elf 파일 구조는 pe 파일 구조에 비해 제가 조금 부족하게 적어놨는데요...
      나중에 추가적으로 공부한 내용있으면 또 포스팅하꼐요!!
      열공하세요~!

  2. 오우 2015.03.26 21:21  댓글주소  수정/삭제  댓글쓰기

    잘 보고 갑니다 ~~

※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


어제에 이어서 다음에 이어질 내용을 공부해보자. object file 중에서 relocatable object file을 중점적으로 보기로 했다. 이전에도 말했지만 object file이 별게 아니다. 그냥 우리가 구현한 C 소스에서 사용한 함수나 변수 혹은 그 외의 여러 정보들을 일정 포멧에 맞춰서 binary 형태로 나열한 단순한 파일일 뿐이다. 그것을 우리는 유식하게 object file이라고 부르는 것 뿐이다. 그렇다면 relocatable object file은 어떤 형태를 띄고 있을까? 아래와 같다. 



하나의 relocatable object file은 위와 같은 형태의 구조로 되어있다. 당연한 말이지만 ELF header 역시 나름의 구조가 있다. 그런데 지금은 그냥 ELF header라는 것이 존재한다는 것만 기억하고 넘어가보자. 


위의 그림에서 각각이 어떤 내용을 담고 있는지 이야기해보자.


.text

이 부분은 코드를 포함하고 있다. 물론 printf();와 같은 형태의 코드가 아니라 machine code를 의미한다. 


.rodata

read-only data 영역이다. 수정하지 못 하고 읽기만 할 수 있는 영역이라는 말이다. 그렇다면 이 부분에는 어떤 정보들이 들어가 있을까? 가령 printf("hello world");라는 함수를 소스파일에서 호출했다면 "hello world"라는 스트링이 저장되기도 한다. 또는 switch 문에서의 jump table 등이 이 영역에 저장된다고 한다. 


.data

말 그대로 data 영역이다. 이 부분은 global variable 중에서 초기화된 global variable의 정보를 포함한다. global variable로 아래와 같이 선언되었다고 가정해보자.

int a = 1;

int b;

a는 선언도 되고 초기화도 되어있는 것을 알 수 있고, b는 선언은 되어있되 초기화는 되지 않은 것을 알 수 있다. data 영역은 int b에 대한 정보는 담지 않고 초기화된 int a에 대한 정보를 담고 있다. 그렇다면 초기화되지 않은 int b와 같은 정보는 어디에 포함되는가?


.bss

이 부분이 바로 초기화되지 않은 global variable에 대한 정보를 포함한다. 여기서 하나 중요한 부분을 집고 넘어가자. 이 부분이 .data section과 다른 점은 바로 실제 사이즈가 0이라는 점이다. 아래의 예시를 보자.



위의 swap.c에서 보면 bufp0은 선언과 초기화가 되어있고 bufp1는 선언만 되어있다. 따라서 bufp0은 .data section에 포함되며 bufp1은 .bss section에 포함된다. swap.c로부터 생성한 swap.o 파일을 readelf 명령어를 통해 확인해보면 아래와 같은 내용을 확인할 수 있다.



.bss 영역의 크기는 0이다. 반면 .data 영역의 크기는 4 byte이다. (이 결과물을 얻기 위한 objdump 명령어의 option은 --headers이다. 이 옵션은 section header 정보를 프린트해준다.) 실제 section 내용을 볼까? 아래의 내용은 section 내용의 일부라는 점을 알아두자. 



.data section 후에 바로 .comment section 으로 넘어간 것을 알 수 있다. .comment section 은 위의 ELF format 그림에서 다뤄지지 않은 부분이다. 조금 구글링을 통해 알아보니 아래의 링크에서 어느 정도의 답을 찾을 수 있었다.

http://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/specialsections.html


특별한 형태의 section 중의 하나가 .comment section이라네... 아무튼 중요한 사실은 .bss 영역이 안 보인다는 것이다. 그렇다 .bss 영역은 section header에는 포함되는 내용이나 실제로 section에서 사이즈를 차지하고 있지는 않다는 것이다. 


.symtab

symbol table 영역을 의미한다. symbol이란 무엇일까? symbol은 하나의 object file이 가지고 있는 변수나 함수 또는 다른 추가적인 정보들을 각각 symbol이라고 한다. 따라서 하나의 object file에는 여러 개의 symbol이 있을 것이다. 아래의 그림을 보면 어떤 정보를 포함하는지 확실히 알 수 있을 것이다.



readelf tool을 이용해서 main.o 파일을 확인해보면 위와 같이 나와있다. 여기서 말하는 main.o의 c파일인 main.c는 아래와 같다. 아래의 코드와 잘 비교해서 봐보라.



C 파일에서 사용한 함수 변수명 등이 symtab에 있다. 그런데 위의 두 그림으로는 알 수 없는 것이 한 가지 있다. local variable이 사용되면 어떻게 될까? local variable은 위의 symtab에 나오지 않는다. 이 부분은 프로그램 실행시 stack에 임시적으로 쌓였다가 사라지는 부분이므로 symtab에서는 확인되지 않는다.



.rel.text

.rel.data : 

이 부분은 각각 .text와 .data section을 relocation할 때 필요한 정보들이다. 이 부분에 대한 자세한 relocation algorithm은 나중에 설명할 예정이다.

 

.debug : 

.line : 

위의 두 부분은 컴파일할 때 -g 옵션을 통해 컴파일되어서 디버깅 관련 정보를 포함하고 있을 때만 선언된다.


.strtab : 

strtab은 문자열에 대한 정보를 포함하는 부분인데 정확하게는 잘 모르겠다.


많은 중요한 정보들이 있다. 하지만 위의 정보 중에서 가장 중요한 정보는 아마도 symbol이나 symbol table에 관한 정보가 아닌가 싶다. 다음 포스팅에서는 symbol에 대해서 조금 더 자세하게 공부해보자. .rel.text와 .rel.data에 대해서는 굉장히 중요한 부분이라고 나는 생각한다. 따라서 그 부분 역시 나중에 자세하게 하나의 포스팅으로 다룰 생각이다.

Posted by 빛나유

댓글을 달아 주세요