« Previous : 1 : ... 5 : 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : ... 33 : Next »

부자가 되지 못하는 인간의 생리적 본능


1) 무리 짓는 본능


: 누군가 지나가다가 하늘을 한참 바라보고 있으면, 곧 많은 사람들이 그 행동을 따라하게 된다.

  먼 옛날 인간의 조상은 상당히 왜소한 종족이었다.

  사냥을 할때도 적의 습격을 받을때도 무리를 짓고 있는 것이 성과도 크고 희생도 적었다.

  즉 무리를 짓는 것이 생존에 유리하다는 본능으로 각인되어 현재까지도 남아있는 것이다.

  대부분의 사람이 벤처, 부동산, 주식 붐이 일어나면 나도 해볼까 그러고 시작한다.

  일명 상투잡고 들어가서 눈물만 흘리고 나오게 경우다. 이 역시 무리짓는 본능이다.

  부자들은 무리짓지 않는다. 유행이나 타인의 평가에 흔들리지 않고 냉철한 판단력을 가진 사람들이 부자다.


2) 영토 본능


 : 원시 시대에 남의 영토로 들어간다는 것은 목숨을 거는 위험한 행동이다.

   많은 사회생물학자, 사회학자들은 인간이 오늘날에도 여전히 강한 영토 본능을 가지고 있다고 한다.

   사람은 대부분 자기 지역이 살기 좋다고 남에게 한다. 또한 이사를 다녀도 그 영역을 벗어나는 경우는 적다.

   부동산에 투자를 하고 싶은 젊은이가 있다면 지금부터라도 발바닥이 아프게 돌아다녀야 한다.

   부동산은 생물처럼 유기적이며 인터넷으로만은 알수가 없다. 끊임없이 영토본능을 극복해야만 기회가 오는 것이다.


3) 쾌락 본능


   인간의 행동을 지배하는 건 쾌락을 추구하는 본능이다.

   무엇을 먹을까 고민할때도 결국 그 선택은 쾌락본능에 따르게 된다. 즉, 소위 말하는 땡기는 것을 먹게 된다.

   이러한 본능은 결국 저축보다 소비를 즐길 수 밖에 없고, 절약보다 낭비를 불러일으키게 된다.

   쾌락 본능을 절제하고 통제하게 되는 자기만의 비법을 키워라. 그럼 부와 멋진 몸매를 가지게 될 것이다.


4) 근시안적 본능


   하버드 대학의 사회생물학자 에드워드 윌슨은 "인간은 타고난 근시안"이라고 말한다.

   인간의 유전자에는 "짧은 시간을 선호하는 본능"이 있다는 것이다.

   대부분의 사람은 힘든 고생보다는 "인생은 한방이야"를 외치며 대박을 원한다. (물론 실제로 그런 경우도 있다.)

나, 당신, 우리 모두는 타고난 한탕주의자다. 그렇치만 그건 마치 몰아주기와 같다.

꿈을 가지고 꾸준히 노력하고 공부하라.


5) 손실공포 본능

 

 

  노벨상을 수상한 경제학자 다니엘 카너먼에 따르면 사람들은 손실공포감 때문에 합리적인 투자를 못한다고 한다.


  투자는 언제나 위험한 게 사실이다. 그러나 가장 큰 위험은 아무 투자도 하지 않는 것이다.


 

6) 과시 본능

 

 

 많은 동물들이 짝짓기를 할때 화려한 색깔, 과장된 몸짓 등으로 과시를 하면 자기의 위상을 높인다.

인간 역시 이전부터 자신의 지위를 확보하기 위하여 과시라는 본능을 형성하였다.

고급차, 명품 소비만 봐도 그러한 본능을 충분히 이해할 수 있다. 남보다 잘난 내 자신은 얼마나 큰 행복감을 주는가...

그러나, 그것이 능력이 아닌 과시라면 당신은 부자가 될 수 없다.

원하는 것과 필요한 것을 구분하여 소비하라. 젊을때 절제된 행동은 당신에게 부와 기품을 가져다 줄 것이다.

 

7) 도사 환상

 

 

  도저히 이해안되는 현상에 대하여 원시인들이 주술사를 필요로 했듯이, 인간은 알기보다는 믿기를 좋아한다.


  수많은 전문가, 서적, 전문가들이 미래를 예측해주고 재테크 조언을 해준다.


  그들은 일반적인 현상에 대한 세일즈를 하고 수고비를 받는 사람들이다.


  전문가 의견은 참고는 하되 판단은 당신의 몫이다. 묻지마 투자는 곤란하다.


 

8) 마녀 환상

 

 

  부자를 미워하는 것은 소외된 자의 오랜 전통으로 남았다.


  마녀 사냥을 하듯이 나의 불행은 그들의 탓으로 돌리는 게 맘 편하다.


  지금의 가난이 내 잘못이 아니고 남 탓이라면 부자기 되기 위한 성장동력을 잃어버리게 된다.


  남을 비판하고 비난할게 아니라 자본주의 룰에 적극적으로 적응해라.


  그렇치 않다면 그냥 종교에 귀의하고 마음의 평화를 찾아라.


 

9) 결함있는 인식 체계

 

 

 인간은 보고 싶은 것만 보도록 진화했다.

 

좀 더 구체적으로 "행운 편향 인식", "모르는 것도 안다는 착각", "돈과 자신에 대한 부정적 해석"이 있다.

 

자신이 행운을 맞을 가능성은 과대평가하게 되고, 왠지 자기가 잘 알고 있다는 묘한 착각과 더불어 실패에 했을때 오는 처절한 자기 비하가 그것이다.

 

부족한 자기 자신을 솔직히 인정하고 항상 자신에게 주문을 걸어라....

난 할수 있다. 난 할수 있다.

출처 :
서적 <내 안의 부자를 깨워라>

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.

Leave your greetings here.

  
  
  
  
  
  
  
  
 


1.2008 세계 증시 전망


1) 성장성
- 미국 경기 침체 기정 사실화
- 전체 성장률은 글로벌 금융시장의 불안 여파로 낮아질 것이나 이머징 및 아시아의 성장률은 여전히 높을 것


2) 유동성
- 중국, 인도를 비롯한 이머징 시장의 성장성을 바탕으로 아시아, 이머징의 유동성 증가
- 달러화 약세는 이미 굳어진 추세
- 미국 유동성 악화, 서구 유럽시장 유동성 다소 증가


3) 변동성

- 서브프라임으로 인한 신뢰 하락(미투자은행의 부실규모 속이기)
- 서브프라임으로 인한 부실이 파생상품들을 통해 전이될 것을 우려하여 투심이 극도로 악화
- 미국 경기 침체 기정 사실화
- 전체적 성장률 하락과 이머징-아시아 유동성 증가로 인한 마찰로 변동성은 예년보다 증가


4) 세계증시전망 정리
- 이미 외국투자가나 국내 전문가(미래)들은 미 경기 침체를 기정사실화하여 세계 경제의 성장률은 다소 낮아질 것으로
판단하고 있으나 중국, 인도를 비롯한 이머징 마켓이 견인차 역할을 하여 점차 상승할 것으로 기대
(여러 지표 분석 및 자료를 종합해봐도 역시 최고의 투자처로 중국, 인도가 꼽힘)
- 전체적인 변동성 증가로 인해 안정적인 형태의 분산을 권고
- 장기투자시 역복리 효과로 인한 손실에 유의하여 분산 투자를 매우 중시함
- 1/4분기 중 투자 신중


5) 참고
- 예년에 비해 국내 투자가들의 포트폴리오 권고시 서유럽 및 일본에 대한 권고가 빠짐
( 성장 추세를 믿었으나 오히려 하락한 일본 )
- 추천 포트폴리오의 대다수가 브릭스로 몰림
- 브릭스 각국과 브릭스의 상관관계를 한국과 비교했을 때 브릭스와 한국의 상관관계가 가장 낮으므로
매우 적절한 분산으로 판단 ( 러시아와 인도의 상관관계도 매우 낮은 편 0.38)



2. 전망에 의거한 브릭스 시장 및 한국 시장 분석


1) 중국
- 미국과 이머징 시장의 디커플링 심화
- 3월말 총리교체 및 올림픽을 전후로 한 안정적인 성장을 원하는 중국지도부
- 상해의 GDP비중은 여타 개최지에 비해 미미 (상해3.4 서울25.1 시드니32.3)
- 경제규모가 큰 나라일수록 올림픽이 경기 전환점이 될 가능성이 낮음
- 세계경제 성장 탄력 둔화는 중국의 수출여건에도 부담이나 내수소비시장의 성장이 중국의 신동력이 될 전망
- 중국 내수시장 성장의 수혜국은 주변 아시아국이 될 가능성이 높음
- 약달러 기조로 인한 유동성의 NON-US 지역으로의 유입 자극
- 국부펀드의 대규모 유동성의 아시아 유입 본격화
- 홍콩은 본토증시와 시차를 두고 유사한 움직임을 보임(본토시장이 홍콩을 리드)
(홍콩직투 문제는 본토(내국인)와 홍콩(외국인)중국정부의 유동성 이동용 도구로 그 실행여부, 시기를 판단하기 힘듬)
- 고평가 논란에도 불구하고 여전히 중국의 성장성이 가장 높은 것으로 판단
- 현 H지수 16000에서 20-25%의 수익(19500가량) 실현 후 일부 현금화 고려


2) 국내
- 한국의 미국과 디커플링 강화, 중국과 커플링 강화되는 추세(중국내수시장 성장에 대한 수혜예상)
- 예금에서 펀드로의 자금이동은 정해진 추세이며 가속화될 전망
- 한국증시의 체질개선으로 인한 국제변동에 대한 내성 증가
- GDP규모 대비 펀드규모의 불균형 해소(아시아평균 32%, 세계평균 53%, 한국29%, 인도6%)
- 변액연금, 연기금의 주식 비중 확대로 인한 기관 수급 여력의 강화
- 국내 기관투자가(미래)는 11월부터 유입된 자금의 대부분을 주식매수 대신 현금으로 보유했으며 추세전환 확인 후
본격적은 매수에 나설 예정
- 1/4분기 저점(1800), 3/4분기 고점(2000가량) 의 차이 10-15%가량의 수익 얻고 변동성에 대비


3) 인도
- 2007년 1-8월간 6회 금리인상으로 인해 주가가 바닥을 형성
- 2007년의 중국을 제외하면 최고로 변동성이 심했던 시장
- 외국인 투자자 비율이 40%에서 22%로 감소 (인도의 자신감)
- 미국의 경기침체 우려와 인도산업의 40%를 차지하는 IT산업으로 미루어 현 상황에서 인도주가도 하락해야 하나 인프라를
중심으로 하는 내수시장의 성장을 바탕으로 견고한 상승을 계속 중
- 미래측 또한 실수(최근 친디아펀드의 인도비중 축소)를 인정하고 다시 인도비중을 이전 수준으로 늘림


4) 러시아
- 인도와 유사하게 2007년 1/4에서 2/4분기까지 상승하지 못했던 여력이 최근 탄력을 받음
- 3월 대선을 통한 정치불안요소 제거 여부가 관건
- 유가 상승은 지속될 듯


5) 라틴
- 원자재를 바탕으로 최근 탄탄한 성장세를 보여주고 있으나 미경제 침체로 인한 영향력이 우려되며 변동성이 매우 큼


6) 말레이시아와 인도네시아를 비롯한 아세안
- 대안투자처이나 화교자본 및 외국인 투자자의 영향이 크며 주요국과의 상관관계가 높은 편
- 수익면에서는 브릭스, 안전성면에서는 브릭스 분산에 미치지 못하거나 중복될 확률이 높음



3. 정리


- 미경기 침체를 기정사실화
- 성장률 하락과 유동성 증가로 인한 마찰이 변동성을 키우므로 철저한 분산 투자를 권유(브릭스,아세안,한국,인프라등)
- 브릭스중 인도와 러시아가 유망하다고 볼 수 있으나 철저하게 안전을 고려한 투자가 필요
( 인도 : 인프라 적립식, 러시아 : 3월 대선 전후 상황 고려 )
- 인도투자는 10월 말 중국처럼 상투 잡을 확률이 존재
- 러시아와 브라질은 미 경제에 대한 의존도가 매우 높으므로 리스크가 큰 편
- 두바이 vs 싱가폴 (두바이의 건물은 화려하는 공실이 높음, 싱가폴과 같은 언어, 금융, 전체적 인프라 부족)
- H, 항셍지수의 올림픽 전후의 폭등을 통한 8월 같은 스프링 효과는 기대하기 어려움(올림픽 특수의 선반영)
- 중국, 인도를 필두로한 아시아-이머징시장이 세계경제 성장의 견인차 역할을 수행



4. 대응


1) 수익 실현 포인트 (총자금의 50%)
- 코스피 1950선
- H 19000 - 20000


2) 미차솔1
- 1월 30일 FRB 금리발표 전후 상황
- 본토지수 향방 파악
- 올림픽 전후 변동성 증대 가능성 하락
- 올림픽으로 인한 투자자의 심리적 불안 및 적정수익 실현 세력, 경기침체 우려, 서브프라임 공포
                                     vs
  안정적 상승, 부의 분배, 이미지 개선, 내국인 자금 보호를 위한 중국정부 의지

등을 고려하여 투입 예정 자금의 향방 결정


3) 포트변경 고려
- 한국밸류(신영마라톤, 신영고배당)
- 슈로더 라틴(맵스 라틴, 우리cs 브러시아, 우리cs 러시아)의 적립식 전환
- 짐로저스의 판단처럼 중국 본토투자가 여전히 유효할 듯 (용가리 유지)
- 미래인디아인프라 펀드 적립식 가입 고려
- 인사이트 환매후 RCF나 15% 영진에게 투자
- 맵스 로저스 상품 파생투자 신탁 적립식 투자 시작 (헷지용)
- EMEA 펀드 및 싱가폴 관련 펀드 분석
- CJ지주회사의 디스1 편입( MB효과 분석 후 )
- 해외장마펀드 확대(원자재 관련)


4) 기타
- 1/4분기 추불 자제
- 현대차 미국 사정 악화 (미 경기침체--3대에서 1대로--1호 차량으로 도요다등을 고름, 국내비중비대)
- LG전자 기대 (이머징 판매 호조)
- 금감원과 금감원 자산운용국 문의 결과 장마펀드의 경우 2009년 이후에도 해외주식매매차익 비과세 인정으로 판정
- 증여세 불필요 확인 (거액자산가나 고려)
- 증여 필요시 예금불입 후 증여세 신고하고 예금 해지 후 투자해도 무방
- 증여 자산으로 부동산만 안사면 증여세 문제는 해결


PS : 매번 도움만 받아왔는데 요청하신 분도 계시고 혹시 도움이 될까 싶어서

      오늘 세미나 정리한 내용에 제 견해를 첨가해 보았습니다.

      오류라 판단되는 부분들에 대해 고수님들의 따끈한 가르침 부탁드립니다.


PS2 : 마지막 기타의 장마펀드 문제는 오늘 자산운용국에 직접 문의한 결과 입니다

출처 : 모네타 구티(gkj99)의 글

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.

Leave your greetings here.

  
  
  
  
  
  
  
  
 

미디음악

2008/01/09 07:20 / Flavor
생애처음...
음악의 모든 기본을 무시하고
(뭐 사실 음악이란걸 잘 알지도 못하지만.)

그냥 기분따라
미디음악이란걸 만들어 봤다.

근데...
공들인 시간과 정성이 부족해서인지...

이렇게 올려놓고 보니
너무 형편없다...

나처럼...
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.

Leave your greetings here.

  
  
  
  
  
  
  
  
 

The Linux GCC HOWTO

2008/01/07 11:24 / Resource

GNU C 컴파일러와 라이브러리를 리눅스 상에서 어떻게 셋업하는지에 대해 다루고 있다. 그리고 리눅스 상에서 컴파일, 링킹, 실행, 디버깅을 어떻게 하는지에 대하여 개략적인 지식을 제공한다. 대부분의 내용은 Mitch D'Souza씨의 GCC-FAQ로부터 차용해온 것이며 (많은 부분 교체했다.) 또한 ELF-HOWTO로부터도 차용을 해온 것이다. (이것도 또한 대부분 바뀌게 될 것이다.) 이 문서는 첫번째 공개 버전이다. (버전 번호는 RCS 의 장난일 뿐이다.) 여러분의 의견을 환영한다.

1. 시작하는 말

1.1 ELF vs. a.out

리눅스 개발은 지금 현재에도 끊임없는 변화 과정에 놓여 있다. 간단히 말해서, 리눅스의 측면에서 어떻게 실행해야 하는지 알고 있는 바이너리는 바로 이 2 가지 종류가 있다. 여러분의 시스템이 어떻게 구성되어 있는지에 따라 둘 다 가지고 있을수도 있다.

2 가지를 어떻게 구별하는가? file이라고 하는 유틸리티를 사용하면 된다. ELF프로그램에 대해서는 ELF 라고 어쩌구 저쩌구 말할 것이며, a.out 프로그램에 대해서는 Linux/i386이라는 단어가 들어가는 말로 얘기해줄 것이다.

둘 간의 차이는 문서 후반부에서 설명될 것이다. ELF 는 새로운 실행화일 형식이며, 일반적으로 더 뛰어나다고 여겨지고 있다.

1.2 책임(Admistrata)

저작권에 관련된 정보는 이 문서의 마지막을 참고하라. 또한, 후반부에서 이 글을 읽고, Usenet에 바보같은 질문(존재하지 않는 GCC의 버그를 발표하는 등)을 올리지 말라는 경고를 볼 수 있을 것이다.

2. 필요한 것을 어디에서 얻을 수 있는가?

2.1 지금 이 문서

이 문서는 리눅스 하우투 문서 시리즈의 하나이다. 따라서 모든 리눅스 하우투 문서가 저장되어 있는 곳이라면 어디든 있다. 예를 들어서 http://sunsite.unc.edu/pub/linux/docs/HOWTO/와 같은 곳이 바로 그곳이다. HTML 버전은 http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html에서 찾을 수 있으며 약간 버전이 높을 지도 모른다.

2.2 다른 문서들

gcc 에 대한 공식적인 문서는 소스 배포 화일에 들어있다. texinfo 화일, .info화일의 형식으로 들어있다. 네트워크 속도가 빠르다거나, 시디롬에 가지고 있거나, 또는 인내심이 많다고 생각될 때에는 그것을 untar 한 후에 해당 화일을 /usr/info디렉토리에 카피하도록 하자. 만약 없다면 tsx-11에 가서 자료를 찾아보자. 항상 최신 버전이 있는 것은 아닐 것이다.

libc 에 대한 문서는 2 가지가 있다. GNU libc 의 경우에는 info 화일들을 가지고 있는데 stdio 부분을 빼고는 아주 자세히 리눅스 libc 에 대해서 알려주고 있다. 맨페이지도 구할 수 있는데 시스템 호출(system call 섹션 2), 많은 libc 함수(섹션 3)에 대해 아주 상세히 설명하고 있다.

2.3 GCC

두 가지 답이 있다.

(a) 리눅스 GCC 의 공식적인 배포판은 ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/에서 바이너리 형태로 구할 수 있다. 즉 이미 컴파일되어 있는 것을 말한다. 지금 글을 쓰고 있는 이 순간에 최신 버전은 2.7.2 로서 화일명은 gcc-2.7.2.bin.tar.gz이다. (편집자주: 이 문서의 작성시기가 96년도임을 상기하라!)

(b) FSF로부터의 최신 소스 버전은 GNU 프로그램 저장소인 GNU archives에서 구할 수 있다. 소스 버전이 항상 공식배포판 바이너리 버전과 같은 것은 아니다. configure 스크립트를 이용해서 모든 설정을 할 수 있다. tsx-11도 마저 살펴보도록 하자. 패치화일이 필요할 지도 모르기 때문이다.

어떤 것이든 컴파일이라는 것을 하기 위해서는 다음이 필요하다.

2.4 C 라이브러리와 헤더 화일들

여기서 여러분에게 필요한 것은 일단 (1)여러분의 시스템이 a.out 인가? (2) 아니면 둘 다 있는 경우에 둘 중에 무엇을 택하고 싶은가? 에 따라 달라진다. 만약 여러분이 libc 4 에서 libc 5 로 업그레이드하려고 한다면 우선은 ELF-HOWTO문서를 봐야할 것이다.

tsx-11에서 구할 수 있다.

libc-5.2.18.bin.tar.gz

--- ELF 공유 라이브러리 이미지, 정적 라이브러리 그리고 C 라이브러리와 수학 라이브러리를 위한 헤더화일들

libc-5.2.18.tar.gz

--- 위 라이브러리에 대한 소스. 여러분은 헤더 화일을 구해야 하기 때문에 위에 있는 바이너리배포판도 필요하다. 손수 컴파일을 할 것인지 아니면 그냥 바이너리를 사용할 것인지에 대한 답은 간단하다. 바이너리를 사용하라! 하지만 NYS나 셰도우 패스워드 기능을 원할 때는 손수 컴파일하는 수 밖에 없다.

libc-4.7.5.bin.tar.gz

--- a.out 공유 라이브러리 이미지, 정적 라이브러리(C 함수, 수학 함수), 위에 있는 libc 5 와 공존할 수 있게끔 디자인되어 있다. 하지만 여러분이 a.out 프로그램을 아직도 갖고 있거나 개발하려고 할 때만 필요하다.

2.5 관련된 도구들 (as, ld, ar, strings 등등)

tsx-11에서 구할 수 있으며, 현재 버전은 binutils-2.6.x.x.bin.tar.gz이다.

바이너리 유틸리티들은 오로지 ELF 만 있다는 사실에 유의하자. 현재 라이브러리는 ELF 로만 개발되고 있으며 a.out 라이브러리는 ELF 와 같이 쓸 때만 의미있다고 생각한다. C 라이브러리 개발은 ELF 쪽으로만 진행되고 있으며, a.out으로 해야할 커다란 이유 같은게 없다면 그에 따르는 것이 좋다.

3. GCC설치와 설정

3.1 GCC 버전

현재 사용 중인 gcc 의 버전을 알고 싶은 경우에는 gcc -v라고 셸 프롬프트에서 실행시키면 된다. 또한 이렇게 명령을 내리면 여러분의 시스템이 ELF로 세팅되어 있는지 아니면 a.out 으로 되어 있는지 확실하게 알아낼 수 있다. 필자의 시스템에서는 다음과 같이 나온다.

$ gcc -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2

여기서 알아두어야 할 핵심적인 내용은 다음과 같다.

  • i486. 이는 여러분이 486 프로세서 용으로 컴파일된 gcc를 사용하고 있다는 말이다. 이 부분은 다를 수 있는데 어떤 사람은 386, 586 에 따라 다를 수도 있다. 하지만 이 3 가지 칩에서 컴파일된 것들은 상관없이 서로 잘 실행된다. 차이점이라고 한다면 486 코드가 어디엔가 더해짐으로써 486 에서는 더욱 더 빨리 실행된다는 정도이다. 386 에서 실행하는데 해가 된다거나 하지는 않는다. 하지만 약간 바이너리가 커질 것이다.
  • box. 이건 전혀 중요한 부분이 아니다. 예를 들어서 box라는 말 대신에 slackwaredebian 등의 단어로 교체될 수도 있고 아예 이 부분이 없을 수도 있다. 보통은 i486-linux 이런 식일 것이다. 만약 gcc 를 컴파일해서 사용한다면 본인이 따로 i486-box-linux 라고 지정했듯이 gcc를 만들 때 정해줄 수 있다.
  • linux. 이 단어 대신에 linuxelf 라든가 linuxaout이라는 단어가 들어갈 수도 있다. 또는 리눅스 커널 버전이 들어가도록 할 수도 있다. 암튼 리눅스용임을 잘 나타내고 있다. 간단히 결론을 말하자면, 이 단어의 뜻은 사용중인 GCC 버전에 따라 다르게 해석된다.
    • 2.7.0 이상의 버전에서는 그냥 linux이면 ELF 를 의미하고 a.out은 linuxaout 과 같은 이름을 갖는다.
    • 리눅스가 ELF 쪽으로 나아가면서 이름이 linux에서 밀려났다고도 할 수 있다. 따라서 2.7.0 그 이하에서는 linuxaout 이라는 말을 찾아볼 수 없을 것이다.
    • linuxelf라는 이름은 사라진 말이다. gcc 버전 2.6.3 시절에 ELF 실행화일을 만들기 위해서 지어졌던 이름이다. gcc 2.6.3 은 ELF 실행화일을 만드는데 버그가 있다고 알려져 있다. 업그레이드하기 바란다.
  • 2.7.2 이것은 버전 번호이다.

따라서 종합해보면 필자는 지금 ELF 실행코드를 생성시키는 gcc 2.7.2 를 가지고 있다는 것이다.

3.2 도대체 내 gcc 가 어디에 있는건가?

그냥 아무 생각없이 gcc 를 설치했거나 배포판을 설치할 때 자동으로 설치하게 했다면, 도대체 리눅스 화일 시스템 상에서 어디에 위치하는지 알고 싶을 것이다. 대답은 이렇다.

  • /usr/lib/gcc-lib/target/version/ (그리고 모든 하위 디렉토리들)이 컴파일러의 대부분이 위치하는 장소이다. 컴파일을 수행하는 실행화일 그 자체와 gcc 버전에 따른 라이브러리와 헤더화일들이 들어있다.
  • /usr/bin/gcc는 컴파일러 운전사(Compiler Driver)역할을 한다. 커맨드 상에서는 gcc 라고만 명령한다. 만약 여러 버전의 컴파일러를 가지고 있다면 여러 버전과 함께 사용할 수 있다. gcc 가 사용하게 될 디폴트 버전의 컴파일러를 알아내기 위해서는 gcc -v라고 해보면 된다. 다른 버전으로 강제로 컴파일하게 하려면 gcc -V version 이런 식으로 사용하면 된다. 예를 들어서...
    # gcc -v
    Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
    gcc version 2.7.2
    # gcc -V 2.6.3 -v
    Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specs
    gcc driver version 2.7.2 executing gcc version 2.6.3
    
  • /usr/target/(bin|lib|include)/. 여러분이 여러 개의 목표 형식을 가지고 있다면 (일단 ELF인가 a.out 인가 또는 여러 형태의 크로스 컴파일러 등) 디폴트 목표 형식용이 아닌 라이브러리, 바이너리 유틸리티 (as, ld 등...), 헤더 화일들도 찾아볼 수 있을 것이다. 오로지 한 종류의 gcc 를 가지고 있다 하더라도 매우 많은 것들이 그 디렉토리에 깔려있음을 확인할 수 있다. 그렇지 않다면 아마도 /usr/(bin|lib|include)에 있을 것이다.
  • /lib/,/usr/lib 그리고 여타 라이브러리 디렉토리들이 기본 시스템을 위한 라이브러리 디렉토리이다. 여러분은 또한 상당히 많은 프로그램에 대하여 /lib/cpp를 가지고 있어야 한다. (X 가 실제로 많이 사용하고 있다.) /usr/lib/gcc-lib/target/version/에 있는 cpp 를 카피해놓던가? 아니면 심볼릭 링크를 해준다.

3.3 헤더 화일들은 어디에 있는가?

여러분이 손수 /usr/local/include에 설치한 것들 빼고 리눅스에는 3 가지 중요 헤더 디렉토리가 있다.

  • 대부분의/usr/include/와 그 하부 디렉토리들은 H J Lu 의 libc 바이너리 배포판에 의해서 제공된다. 여기서 본인은 "대부분"이라는 표현을 썼는데, 그 이유는 다른 소스 (예를 들어 curses, dbm 라이브러리)에서 온 헤더화일들도 있기 때문이다. 특히나 최근 libc 배포판을 가져오면 그러한 헤더화일들은 없다. (예전에는 같이 달려서 왔지만)
  • /usr/include/linux/usr/include/asm(<linux/*.h>화일과 <asm/*.h>에 의해 참조되는 헤더화일들이 있는 장소)는 각각 커널 소스에서 linux/include/linuxlinux/include/asm을 가리키는 심볼릭 링크여야 한다. 뭔가 조금이라도 큰 작업을 하려고 한다면 분명히 설치해야 한다. 커널 컴파일을 하기 위해서만 있는 것은 아니다.

    또한 커널 소스를 풀고 나서 make config라는 작업을 해주어야 할 것이다. 많은 화일들이 그 과정을 통해서 생겨나는 <linux/autoconf.h>라는 화일에 의존하기 때문이다. 그리고 어떤 버전의 커널에서는 asm 이라고 하는 것이 심볼릭 링크일 뿐, make config 할 때만 생기는 경우가 있다.

    asm 은 보통 asm-i386으로 링크되어 있다. 그전에는 오로지 인텔 머신용 헤더화일만이 있었기 때문에 asm 만이 있었지만 이제는 리눅스가 명실상부하게 멀티플랫폼 운영체제로 나아가고 있기 때문이다. asm-i386말고도 asm-alpha, asm-generic, asm-m68k, asm-mips, asm-ppc, asm-sparc등의 헤더 화일 디렉토리가 있는 것을 발견할 수 있다.

    따라서 /usr/src/linux라고 하는 디렉토리에 이미 소스를 풀어놓았다면...

    $ cd /usr/src/linux
    $ su
    # make config
    [answer the questions.  Unless you're going to go on and build the kernel
    it doesn't matter _too_ much what you say]
    # cd /usr/include
    # ln -s ../src/linux/include/linux .
    # ln -s ../src/linux/include/asm .
    

  • <float.h>, <limits.h>, <varargs.h>, <stdarg.h> 그리고 <stddef.h> 등의 화일들은 컴파일러 버전마다 다를 것이다. 그리고 그들은 /usr/lib/gcc-lib/i486-box-linux/2.7.2/include/에 위치하고 있다.

3.4 크로스 컴파일러(Cross Compiler) 만들기

목표 플랫폼으로서의 리눅스

여러분이 지금 gcc 소스 코드를 가지고 있다고 생각하겠다. 보통은 GCC 에 대한 INSTALL 화일에서 지시하는 대로 따르면 된다. configure --target=i486-linux --host=XXX 이런 식으로 해주는데, XXX는 플랫폼을 말한다. 다음에는 make 과정을 거치면 된다. 리눅스 헤더화일, 커널 헤더화일이 필요하며, 크로스 컴파일러와 크로스 링커를 만들기 위해서도 필요하다. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/에서 구할 수 있다.

소스 플랫폼으로서의 리눅스, 목표 플랫폼으로서의 MSDOS

흠. 소스를 리눅스에서 작성한 뒤에 도스에서 돌아가는 프로그램으로 컴파일하기 위해서는 emx 패키지나 go extender라는 것을 필요로 한다. ftp://sunsite.unc.edu/pub/Linux/devel/msdos에 가서 관련 화일을 찾아보기 바란다.

본인으로서는 테스트해본 적이 없으며, 쓸만하다고 단언하기는 힘들다.

4. 포팅과 컴파일링

4.1 자동적으로 정의되는 심볼들

여러분은 여러분이 갖고 있는 버전의 gcc가 -v 옵션을 붙임으로써 어떠한 심볼을 자동적으로 정의하는지 알아낼 수 있다. 예를 들어 본인의 것은 다음과 같다.

$ echo 'main(){printf("hello world\n");}' | gcc -E -v -
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
 /usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
-Amachine(i386) -D__i486__ -

만약 여러분의 코드가 리눅스에만 관계되는 코드라면, 다음과 같이 해주는 것이 좋다.

#ifdef __linux__
/* ... funky stuff ... */
#endif /* linux */

__linux__라는 이름을 사용하라. linux아니다. 후자가 정의되어 있기는 하지만 POSIX 규격에는 맞지 않기 때문이다.

4.2 컴파일러 부르기

컴파일러 스위치들에 대한 문서는 gcc info 페이지를 보면 된다. (여러분이 Emacs를 사용하고 있다면 C-h i그리고 나서 gcc 옵션을 선택하라) 여러분이 갖고 있는 배포판을 만든 사람이 gcc info 페이지를 넣어지 않았을 수도 있고, 또는 옛 버전의 것이 들어가 있을 수도 있다. 가장 좋은 방법은 ftp://prep.ai.mit.edu/pub/gnu나 또는 미러 사이트로 가서 gcc 소스 코드를 받아오는 것이다. 그리고 그 소스 안에서 카피해온다.

gcc 에 대한 맨페이지(gcc.1)는 일반적으로 시대에 뒤떨어져 있다고 말할 수 있다. 맨페이지를 보려고 하면 그러한 경고 문구를 볼 수 있다.

컴파일러 플래그(flag)

gcc를 사용할 때, -On(여기서 n은 작은 양의 정수들, 생략해도 된다)을 커맨드 라인 옵션으로 넣어주면 출력 코드가 최적화된다. 여기서 사용되는 n 값 중에서 실제 의미를 갖는 값들은 gcc의 버전에 따라 다른데, 일반적으로 0 (최적화하지 않음)부터 시작해서 2(상당히 많이 최적화), 3(아주아주 많이 최적화)까지 쓰인다.

내부적으로 gcc는 이 옵션을 -f-m 이라는 옵션들로 바꾸어서 처리하게 된다. -O의 특정 레벨이 어떤 의미를 갖는지에 대해서는 gcc 실행시에 -v-Q(문서화되지 않았음)플래그를 붙여줌으로써 확인할 수 있다. 예를 들어 -O2는 다음과 같이 나타난다. (사람들마다 서로 다를 수 있다)

enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
-fexpensive-optimizations
         -fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
         -fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
         -fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
         -mno-386 -m486 -mieee-fp -mfp-ret-in-387

여러분의 컴파일러가 지원하고 있는 최적화 레벨보다 큰 숫자를 사용한다면 (예를 들어 -O6), 그 컴파일러가 지원하는 최적의 레벨로 최적화시켜준다. 이런 식으로 컴파일되도록 세팅되어 있는 코드를 배포하는 것은 별로 좋은 생각은 아닌 것 같다. 더 많은 최적화 레벨들이 차후 gcc 버전에 생긴다면, 잘못하면 여러분의 소스 코드가 엉뚱하게 컴파일되는 수도 있다.

만약 여러분이 지금 -O3이 최고 레벨이라는 가정하에서 -O6를 사용했다고 치자. 하지만 다음 버전(예를 들어서 2.7.3.?)에서 -O8까지 지원하게 된다면 -O6는 전혀 엉뚱한 의미를 가질 수도 있다.

gcc 버전 2.7.0 부터 2.7.2 까지의 사용자들은 -O2 최적화 플래그에 버그가 있다는 사실을 잘 알아두기 바란다. Strength Reduction이라고 하는 것이 제대로 작동하지 않는다. 이 문제를 해결할 수 있는 패치가 있고 다시 gcc 를 컴파일해야 할것이다. 또는 언제나 -fno-strength-reduce 라는 옵션을 주고 컴파일하기 바란다.

프로세서별 옵션

-O 옵션을 주어도 자동적으로 작동하지 않는 -m 플래그들이 있다. 하지만 이들은 상당히 유용하다. 중요한 것으로는 -m386-m486이 있다. 이 플래그들은 gcc더러 각각 386, 486중 어떤 것에 더 맞춰서 컴파일할 것인지를 알려주는 것이다. -m486으로 컴파일하였다고 하더라도 386 에서 실행되는데는 지장없다. 그러니 걱정할 필요없다. 486 코드가 조금 더 크지만 386 에서 느려지거나 하지는 않는다.

아직까지는 -mpentium이나 -m586과 같은 것은 없다. 리누스(Linus)는 486 코드옵티마이즈된 코드를 얻으면서도 펜티엄이 사용하지 않는 정렬방식과의 커다란 차이점이 없는 코드를 얻기 위해서는, -m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2를 사용할 것을 제안하고 있다. Michael Meissner(Cygnus에 있는)는 다음과 같이 말하고 있다.

내 육감으로는 -mno-strength-reduce를 같이 쓰면 또한 x86 에서 더 빠른 코드를 얻어낼 수 있다는 것이다. (주의! 나는 지금 strength reduction 버그에 대해서 말하고 있는 것이 아니다. 그것은 전혀 다른 문제이다) 왜냐하면 x86은 다소 레지스터 숫자가 적기 때문이다. (그리고 다른 레지스터에 대하여 레지스터들을 그룹으로 묶어서 spill 레지스터 속으로 처리하는 GCC 의 처리방식은 전혀 도움이 되질 않는다) StrengthReduction은 전형적으로 곱셈을 덧셈으로 교체하기 위하여 다른 레지스터들을 사용하게 된다. -fcaller-saves 또한 이런 문제점이 있지 않나 생각하고 있다.

또 다른 예감은 이렇다. -fomit-frame-pointer는 도움이 될 수도 있고, 그렇지 않을 수도 있다는 것이다. 한 편으로는 또 다른 레지스터가 할당가능하다는 것을 의미할 수도 있고, 다른 한 편으로는 x86 이 연산지시(instruction)에 대하여 인코딩하는 방식으로서, 스택 상대적 주소가 프레임 상대적 주소보다도 더 많은 공간을 차지한다는 것을 의미하기도 한다. 이렇게 되면 프로그램에 사용될 수 있는 Icache이 약간 줄어든다. 또한 -fomit-frame-pointer는 컴파일러가 계속적으로 호출 후에도 스택 포인터를 조정해야 한다는 것을 뜻한다. 따라서 프레임을 갖는 경우, 몇 번의 호출만으로도 스택이 가득 차게 된다.

마지막 말은 리누스 또한 언급하고 있다.

만약 여러분이 최적화된 효율을 원한다면, 나를 믿지 말라. 실제로 테스트를 해봐야 한다. gcc 컴파일러의 옵션은 정말로 많다. 그리고 몇 개의 특정 조합이 가장 좋은 최적화를 이뤄줄 것이다.

Internal compiler error: cc1 got fatal signal 11

시그널 11번은 SIGSEGV, 즉 세그먼테이션 위반에 대한 시그널이다. 일반적으로 프로그램이 포인터를 잘못 썼다는 말이거나 자기가 소유하고 있지 않은 메모리에다 쓰기 작업을 하려고 할 때 발생한다. 그래서 이는 gcc의 버그일 수도 있다.

하지만 gcc는 대부분의 작업에서 매우 안정적이고 테스팅을 많이 거친 소프트웨어라는 사실을 기억하라. gcc는 또한 복잡한 자료 구조와 포인터를 엄청나게 많이 사용하고 있다. 간단히 말하자면 현재까지 소프트웨어 중에서 가장 뛰어난 램 테스팅 프로그램(RAM Tester)이라고 말할 수도 있다. 만약 매번 컴파일할 때마다 멈추는 위치가 다르다면 이는 거의 대부분 여러분 하드웨어의 문제라고 봐도 된다. (CPU, 메모리, 마더보드나 캐쉬) 여러분의 컴퓨터가 파워 온 체킹을 거쳐서 잘 부팅되었고 그리고 윈도우즈 같은 것도 잘 돌아간다고 해서 그것을 gcc의 버그로 돌리지는 말라. 이러한 사실은 무의미하다. 그리고 커널 컴파일하면서 make zImage에서 꼭 멈춘다고 해서 gcc의 버그라고 말할 수는 없다. make zImage는 무려 200개 이상의 화일을 컴파일하고 있다. 그것보다는 좀 작은 경우를 찾아보도록 하자.

만약 계속적으로 버그가 똑같이 나타나고 자그마한 프로그램 컴파일에서도 그러하다면, FSF에다가 버그 리포트를 해도 되고, 또는 linux-gcc 메일링 리스트에 글을 올려도 된다. 그러기 위해서는 우선 gcc 문서를 읽어보고 어떤 절차가 필요한지 숙지한 다음 하기 바란다.

4.3 포팅(Portability)

요즘은 만약 그 소프트웨어가 리눅스로 포팅될 수 없다면 그 소프트웨어는 가치가 없는 프로그램이라고 말한다. :-)

진지하게 말하자면, 일반적으로 리눅스의 100% POSIX 호환성을 이루기 위해서는 아주 약간의 수정작업만이 필요하다. 또한 단지 make 라고만 하면 실행화일이 만들어질 수 있도록 하기 위하여 코드의 원저자에게 수정 코드를 보내는 것이야말로 가치있는 일이다.

BSDisms (bsd_ioctl, daemon 그리고 <sgtty.h>)

여러분은 여러분의 프로그램을 -I/usr/include/bsd를 넣어서 컴파일한 후, -lbsd 옵션을 넣고 링크할 수도 있다. (즉 Makefile 안에서 -I/usr/include/bsdCFLAGS 변수에 넣고, -lbsdLDFLAGS에 넣음으로써) 이젠 BSD 타입의 시그널 행동을 얻어내기 위해서 -D__USE_BSD_SIGNAL덧붙일 필요가 없다. 왜냐하면 -I/usr/include/bsd라고 해주고 <signal.h>를 소스 안에서 포함하면 모든 일이 제대로 이루어진다.

없어진 시그널들(SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS 등)

리눅스는 POSIX를 준수하고 있다. 이러한 시그널들은 POSIX 정의 시그널들이 아니다. 이는 ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990), paragraph B.3.3.1.1 에서 다음과 같이 말하고 있는 바이다.

SIGBUS, SIGEMT, SIGIOT, SIGTRAP, 그리고 SIGSYS와 같은 시그널들은 POSIX.1으로부터 제외되었다. 왜냐하면 그들의 행동은 함축적이고 어떻게 부르느냐에 따라 다르기 때문에 적절하게 범주화시킬 수가 없다. 이러한 시그널들을 없애버리는 것이 규약 준수일 수도 있지만, 왜 그 시그널들을 제외해버렸는지에 대해서 문서화해야 한다. 그리고 그 시그널들을 어떻게 처리할 것인가에 대해서는 아무런 강제 규정도 없다.

이 문제를 해결할 수 있는 가장 간단한 방법은 이러한 시그널들을 모두 SIGUNUSED로 재정의하는 것이다. 바른방법은 물론 이러한 시그널을 처리하는 부분을 #ifdef 문장을 써서 처리하도록 하는 것이다.

#ifdef SIGSYS
/* ... POSIX 규정이 아닌 SIGSYS 코드가 여기에 온다 .... */
#endif

K & R 코드

GCC는 ANSI 컴파일러이다. 하지만 아주 많은 코드들이 ANSI가 아니다. 이럴 때는 컴파일러 플래그에 -traditional 이라고만 붙여주면 된다고 할 수 있다. 물론 괴롭게 수작업을 해줘야 하는 부분도 많이 있다. gcc info 페이지를 살펴보기 바란다.

-traditional라는 옵션은 gcc 가 이용하려고 하는 C 언어 방식을 바꾸는 것 말고도 다른 효과를 지니고 있다. 예를 들어 그 옵션은 -fwritable-strings을 작동시키는데, 문자열 상수를 데이타 영역으로 보내는 역할을 한다. (텍스트 영역, 즉 그들이 쓸 수 없는 영역을 말한다) 이런 경우 프로그램의 메모리 사용흔적(footprint)이 증가하게 된다.

전처리기 심볼이 코드의 프로토타입과 충돌할 때

많이 발생하는 문제들 중에 하나가 바로 몇몇 함수들이 이미 리눅스 헤더화일들에 매크로로 정의되어 있고 전처리기가 코드 내에서 유사한 프로토타입에 대하여 처리 거부를 하는 경우이다. 보통 atoi()atol()인 경우가 많다.

sprintf()

sprintf(string, fmt, ...)이 많은 유닉스 시스템에서는 문자열에 대한 포인터를 반환하는 반면에 ANSI를 따르는 리눅스는 문자열에 삽입된 문자의 갯수를 반환한다. 이는 특히나 SunOS와 같은 것으로부터 포팅하는 경우에 더욱 주의해야 한다.

FD_* 같은 것들? fcntl과 그 비슷한 녀석들. 도대체 정의부분이 어디에 있는가?

<sys/time.h>에 있다. 만약 fcntl을 이용하고자 한다면 실제 프로토타입을 위하여 <unistd.h> 또한 포함시키고 싶을 것이다.

일반적으로 말하자면 어떤 함수에 대한 맨페이지를 보면 SYNOPSYS 부분에서 어떤 헤더화일을 #include 해야하는지 자세히 나타내주고 있으니 그것을 참고하기 바란다.

select()에서 타임아웃이 걸리고 프로그램이 계속 기다리기만 한다.

예전에는 select()에 대한 타임아웃 파라미터가 읽기전용으로만 사용되었다. 그리고 그 때에도 맨페이지에는 다음과 같은 경고가 있었다.

select()는 아마도 적절한 곳에 있는 시간값을 변경함으로써 만약에 그러한 일이 발생한다면 원래의 타임아웃부터 남은 시간을 반환해야 할 것이다. 하지만 이 기능은 차기 버전에서나 구현될 것이다. 따라서 타임 아웃 포인터가 select() 호출에 의하여 수정되지 않을 것이라고 생각하는 것은 바람직하지 못하다.

바로 그 날이 왔다! 최소한 그것이 이루어지고 있다. select()호출로부터 돌아올 때, 타임아웃 인수는 데이터가 도착하지 않는다면 기다리려고 했던 잔류 시간으로 세팅된다. 만약 아무 데이터도 도착하지 않았었다면 이 값은 0(zero)이 되었을 것이다. 그리고 같은 타임아웃 구조체를 가지고 호출을 하게 되면 호출 즉시 되돌아올 것이다.

이 문제를 해결하기 위해서는 타임아웃 값을 매번 select()를 호출할 때마다 관련 구조체에 적어주어야 한다. 다음과 같은 코드가 있다면,

      struct timeval timeout;
      timeout.tv_sec = 1; timeout.tv_usec = 0;
      while (some_condition)
            select(n,readfds,writefds,exceptfds,&timeout); 
아래와 같이 바꾸도록 하라.
      struct timeval timeout;
      while (some_condition) {
            timeout.tv_sec = 1; timeout.tv_usec = 0;
            select(n,readfds,writefds,exceptfds,&timeout);
      }

모자익(Mosaic)의 몇몇 버전이 한 때 이러한 문제로 떠들썩했었다. 회전하는 지구 애니매이션의 속도가 네트워크를 통해 들어오는 자료의 속도에 반비레하는 일이 벌어진 것이다!

시스템 호출이 인터럽트될 때

증상:

프로그램이 Ctrl+Z로 서스펜드되고 다시 시작되어 버린다. 또는 다른 때에는 Ctrl+C와 같은 시그널을 발생시키고 자식 프로세스들을 죽인다 등등... "interrupted system calls" 또는 "write: unknown error" 또는 그런 것 비슷한 에러를 낸다.

문제점:

POSIX 시스템은 다른 구식 유닉스 체제에서보다 약간 더 많이 시그널에 대해서 체킹을 행한다. 리눅스는 시그널 핸들러들(signal handler)을 실행시킬 것이다.

  • 타이머가 째깍댈 때마다 비동기적으로.
  • 모든 시스템 호출 반환시에.
  • 그리고 다음과 같은 시스템 호출 동안에도 그러하다: select(), pause(), connect(), accept(), 터미널 상에서의 read(), 소켓, 파이프나 라인 프린터, FIFO에 대한 open(), PTY나 시리얼 라인, 터미널에 대한 ioctl(), F_SETLKW 명령을 내리는 fcntl(), wait4(), syslog(), 모든 TCP 또는 NFS 작업

다른 운영체제의 경우에는 다음과 같은 시스템 호출에 대해서도 체크할 것이다. 위에서 말한 것 이외에도 다음과 같은 시스템 호출들: creat(), close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(), wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop()

만약 시그널(프로그램에서 핸들러를 인스톨한 경우)이 시스템 호출 중에 발생한다면, 그에 대한 핸들러가 호출된다. 그리고 핸들러가 반환되면 (시스템 호출로), 시스템 호출은 중간에 가로채기를 당했는지 살펴보고 즉시 -1 값을 가지고 반환된다. 그리고errno 를 EINTR 로 세팅한다. 프로그램은 그러한 일이 있을 것이라고 예상하지 못하고 죽는 것이다.

여러분은 다음 2 가지 해결책 중에 하나를 고르면 된다.

(1) 여러분이 설치한 모든 시그널 핸들러에 대하여 SA_RESTART를 sigaction 플래그에 첨가한다. 다음과 같은 것이 있다면,

  signal (sig_nr, my_signal_handler);
를 다음과 같이 바꾼다.
  signal (sig_nr, my_signal_handler);
  { struct sigaction sa;
    sigaction (sig_nr, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
    sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
    sa.sa_flags &= ~ SA_INTERRUPT;
#endif
    sigaction (sig_nr, &sa, (struct sigaction *)0);
  }

이 방법이 대부분의 시스템 호출에 적용되기는 하지만, read(), write(), ioctl(), select(), pause(), connect()에 대해서는 여러분 스스로 EINTR를 체크해주어야 한다. 다음을 살펴보자.

(2) 여러분이 직접 명시적으로 EINTR을 체크해준다.

read()를 사용하는 코드가 원래 이렇게 되어 있다고 치자.

int result;
while (len > 0) { 
  result = read(fd,buffer,len);
  if (result < 0) break;
  buffer += result; len -= result;
}
이 코드를 다음과 같이 바꾸어주면 된다.

int result;
while (len > 0) { 
  result = read(fd,buffer,len);
  if (result < 0) { if (errno != EINTR) break; }
  else { buffer += result; len -= result; }
}
이번에 이런 코드가 있다면,

int result;
result = ioctl(fd,cmd,addr);
그것은 또한 다음과 같이 바뀌어야 한다.
int result;
do { result = ioctl(fd,cmd,addr); }
while ((result == -1) && (errno == EINTR));

BSD 유닉스의 몇몇 버전에서는 시스템 호출을 재개하는 것이 기본 행동으로 되어 있는 경우도 있으므로 주의하자. 시스템 호출이 가로채기를 허용하기 위해서는 SV_INTERRUPT 또는 SA_INTERRUPT 플래그를 사용하도록 하자.

쓰기 가능 문자열 (프로그램이 랜덤하게 세그폴트를 낸다)

GCC는 gcc를 사용하는 사람들이 문자열 상수에 대하여 정확히 상수로서 계속 사용할 것이라고 낙관하고 있는 듯 하다. 따라서 그 문자열 상수를 프로그램의 텍스트 영역에 집어넣는다. 이렇게 함으로써 스왑 영역을 사용하는 것이 아니라 프로그램의 디스크 이미지로부터 페이지 인 & 아웃을 행할 수 있도록 해준다. 그러므로 문자열 상수에 대하여 다시 쓰기 작업을 하게 되면 세그멘테이션 폴트를 일으키게 되는 것이다.

예를 들어서 문자열 상수를 인수로 하여 mktemp()를 호출하는 옛날 프로그램들에서는 문제가 발생할 것이다. mktemp()는 주어진 인수에 다시 쓰려고 하기 때문이다.

이 문제를 고치기 위해서는 (a) -fwritable-strings 이라는 옵션을 주어서 컴파일한다. 이렇게 해주면 gcc는 문자열 상수를 데이타 영역에 넣게 된다. 또는 (b) 문제가 되는 부분을 수정해서 상수가 아니라 변수로 주어지게 만들고 호출 전에 strcpy 를 사용하여 데이터를 그곳으로 카피해준다.

execl()호출이 실패하는가?

원인은 간단하다. 제대로 호출을 하지 않았기 때문이다. execl에 대한 첫번째 인수는 실행하고자 하는 프로그램이다. 그리고 두번째부터는 호출하는 프로그램에 전달할 argv배열이다. 기억하라! argv[0]는 전통적으로 아무런 인수 없이 실행되더라도 세팅이 된다는 사실을! 따라서 다음과 같이 코드를 써야한다.

execl("/bin/ls","ls",NULL);
절대로 다음과 같이 쓰면 안된다.
execl("/bin/ls", NULL);

아무런 전달인수 없이 실행시키는 경우에도 실행형식은 자신의 동적 라이브러리 의존성을 나타낼 수 있는 방식으로 구문을 맞춰준 형태라야 한다. 최소한도 a.out의 경우는 그러하다. ELF는 좀 다른 방식으로 작동한다.

(만약 이러한 라이브러리 정보를 원한다면 아주 간단한 인터페이스가 있다. 동적 로딩Dynamic Loading에 대한 섹션을 보거나 ldd에 대한 맨페이지를 참고하라)

5. 디버깅과 Profiling

5.1 예방적인 관리(lint)

문제가 발생하고 나서 해결하는 것보다는 문제를 미연에 방지하는 것이 중요하지 않을까? 리눅스에 널리 쓰이는 lint는 없다. 아마도 대부분의 사람들이 gcc가 내놓는 자세한 경고 메세지에 만족하고 있기 때문인 것 같다. 아마도 가장 유용하쓰이는 것은 -Wall 스위치일 것이다. 이것이 의미하는 바는 "Warnings, all"로서 모든 경고 메세지를 발생시키라는 말이다. 또한 아주 자세하게 나온다.

Public Domain lint는 ftp://larch.lcs.mit.edu/pub/Larch/lclint에서 얻을 수 있다. 하지만 얼마나 괜찮은지 본인은 모른다.

5.2 디버깅

어떻게 하면 프로그램의 디버깅 정보를 알아낼 수 있는가?

그러기 위해서는 -g 옵션을 주고 컴파일/링크해야 한다. 그리고 -fomit-frame-pointer 스위치는 빼주어야 한다. 사실 모든 부분을 다시 컴파일할 필요는 없고, 여러분이 관심 갖고 있는 부분만을 그렇게 해주면 된다.

a.out에 있어서 공유라이브러리가 만약 -fomit-frame-pointer 스위치를 가지고 컴파일되었다면 gdb를 사용할 수 없을 것이다. -g 옵션을 주는 이유는 바로 정적 링크를 행하라는 말을 함축하게 된다.

만약 링커가 libg.a를 찾을 수 없다고 하면서 실패하게 된다면, 여러분이 /usr/lib/libg.a을 갖고 있지 않기 때문일 것이다. 그 화일은 특별한 라이브러리로서 디버깅 가능 C 라이브러리이다. libc 패키지에 포함되어 있거나 또는 libc 소스 코드를 받아서 컴파일하면 생긴다. 실제로 그렇게 필요한 것은 아니고 대충 /usr/lib/libc.a/usr/lib/libg.a로 링크시켜버려도 대부분 상관없을 것이다.

디버깅 정보를 어떻게 하면 다시 꺼낼 수 있는가?

아주 많은 GNU 소프트웨어들은 -g 옵션을 가지고 컴파일되어 있으므로 화일 크기가 매우 크다. (종종 정적 링크되어 있음) 그렇게 괜찮은 생각인 것 같지는 않다.

만약 프로그램이 autoconf에 의해 만들어진 configure를 가지고 있다면, 보통의 경우 Makefile을 건드림으로써 디버깅 정보를 넣지 않게 할 수 있다. 물론 ELF를 사용하고 있다면, 프로그램은 -g 세팅과는 상관없이 동적 링크되며, 그냥 쉽게 strip(디버깅 정보를 실행화일에서 빼버리는 행위)시킬 수 있다.

관련 소프트웨어

대부분의 사람들은 gdb를 사용하고 있다. gdb는 GNU archive sites에서 소스의 형태로, 아니면 tsx-11이나 선사이트에서 바이너리의 형태로 구할 수 있다. xxgdb는 gdb에 기초한 X 윈도우 디버거이다. 즉, 우선적으로 gdb를 이미 설치했어야 한다는 뜻이다. 그 소스는 ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz에서 찾을 수 있다.

또한 UPS 디버거가 Rick Sladkey씨에 의해 포팅되었다. X 윈도우에서도 잘 돌아간다. 하지만 xxgdb와 같이 텍스트 디버거인 gdb같은 것에 의존하는 형태는 아니다. 아주 훌륭한 기능들을 많이 가지고 있다. 따라서 여러분이 디버깅에 많은 시간을 할애하고 있다면, 우선적으로 UPS 디버거를 권한다. 리눅스용으로 컴파일된 바이너리나 소스 패치화일은 ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/에서 구할 수 있고 오리지널 소스는 ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z에서 찾을 수 있다.

디버깅에 쓰이는 또 다른 툴 하나를 들자면 strace를 들 수 있다. strace는 프로그램이 만들어내는 시스템 호출을 화면에 표시해준다. 이것 말고도 다방면으로 사용가능한데, 예를 들어 어떠한 패스명이 소스코드를 갖고 있지 않은 바이너리 화일 안에 컴파일되어 들어가있는지, 분명히 바이너리 안에 들어있는 조건들을 발견하고자 할 때, 일반적으로 일반적으로 어떻게 작동하고 있는지를 알아내고자 할 때 사용한다. 최신 strace 버전(현재 3.0.8)은 ftp://ftp.std.com/pub/jrs/에서 구할 수 있다.

백그라운드 (데몬) 프로그램

데몬 프로그램들은 전형적으로 fork()를 먼저 하고 나서, 부모 프로세스를 종료시켜 버린다. 이는 디버깅 세션에 대하여 공격적인 요소임이 분명하다.

이럴 때 가장 간단한 방법은 fork에 대하여 정지점(breakpoint)을 지정해주는 것이고 프로그램이 멈추면 다시금 그것을 0 으로 만들어주는 것이다.

(gdb) list 
1       #include <stdio.h>
2
3       main()
4       {
5         if(fork()==0) printf("child\n");
6         else printf("parent\n");
7       }
(gdb) break fork
Breakpoint 1 at 0x80003b8
(gdb) run
Starting program: /home/dan/src/hello/./fork 
Breakpoint 1 at 0x400177c4

Breakpoint 1, 0x400177c4 in fork ()
(gdb) return 0
Make selected stack frame return now? (y or n) y
#0  0x80004a8 in main ()
    at fork.c:5
5         if(fork()==0) printf("child\n");
(gdb) next
Single stepping until exit from function fork, 
which has no line number information.
child
7       }

코어 화일(Core file)

보통 리눅스 부팅시에 코어 화일을 만들지 않도록 세팅되어 있다. 하지만 코어화일 생성을 가능케 하려고 한다면 그것을 다시 가능케 하는 셸의 내장 명령을 사용한다.

셸 호환 셸(예. tcsh)을 쓰고 있다면 다음과 같이 명령을 내린다.

% limit core unlimited

만약 본셸류(sh, bash, zsh, pdksh)를 사용하고 있다면,

$ ulimit -c unlimited

만약 코어 화일의 이름에 대하여 융통성을 가지고 싶다면, 커널 소스를 약간만 변경해주면 된다. 자, fs/binfmt_aout.cfs/binfmt_elf.c와 같은 화일을 찾아보자.

        memcpy(corefile,"core.",5);
#if 0
        memcpy(corefile+5,current->comm,sizeof(current->comm));
#else
        corefile[4] = '\0';
#endif

grep 같은 것을 가지고 이런 부분을 모두 찾은 후에 0이라고 되어 있는 것을 1이라고 모두 고쳐준다.

5.3 Profiling

Profiling이라고 하는 것은 프로그램의 어떤 부분이 제일 자주 호출되고 있는지 또는 많은 시간을 소요하고 있는지를 조사하는 것이다. 코드를 최적화시키고 시간이 가장 많이 소비되는 곳을 고쳐주는 좋은 방법이다. 이렇게 하기 위해서는 -p 옵션을 주어서 시간 정보를 오브젝트 화일들이 가질 수 있도록 다시 컴파일해주어야 한다. 또한 binutil 패키지에 있는 gprof 라는 것을 필요로 한다. 자세한 사항은 gprof 맨페이지를 참고하기 바란다.

6. 링크

호환되지 않는 두 개의 바이너리 형식, 정적 라이브러리와 동적 라이브러리의 구분, 컴파일 과정 후에 일어나는 작업과 이미 컴파일을 마친 실행 프로그램이 실행될 때 일어나는 작업 둘 다에 대하여 "링크"라는 같은 말을 사용하여 생기는 혼란함(사실은 로드(load)한다라는 말에 대한 과부하라고 말할 수도 있다), 이런 모든 것에 대하여 다루므로 이번 섹션은 좀 복잡할 것이다. 말만 어려울 뿐이므로 크게 걱정할 필요는 없다.

이러한 혼란을 완화하기 위해서, 우리는 실행시(runtime)에 일어나는 일에 대하여 동적 로딩(Dynamic Loading)이라는 단어를 사용하겠다. 그리고 다음 섹션에 가서 다루고자 한다. 또는 동적 링킹(Dynamic Linking)이라는 단어로 표현되기도 한다. 이번 섹션에서는 오로지 컴파일 과정 바로 직후에 생기는 링크라는 작업에 대해서만 다루기로 한다.

6.1 정적 라이브러리 vs 공유 라이브러리

프로그램을 만드는 마지막 작업이 바로 링크(Link)라는 과정이다. 필요한 조각들을 모두 모으거나 어떤 부분이 빠져 있는지 알아보기 위한 과정이다. 분명히 프로그램들은 해야할 일이 많다. 이 모든 것을 일일이 다 짜주는 것은 아니다. 예를 들어 화일을 연다든지 하는 일인데 그러한 일들은 이미 여러분에게 라이브러리라는 형태로 주어져 있다. 평범한 리눅스 시스템에서는 /lib/usr/lib/에서 그러한 라이브러리들을 찾을 수 있다.

정적 라이브러리(Static Library)를 사용할 때, 링커는 프로그램이 필요로 하는 부분을 라이브러리에서 찾아서 그냥 실행화일에다 카피해버린다. 공유 라이브러리(또는 동적 라이브러리)의 경우에는 이렇게 하는 것이 아니라 실행화일에다가 단지 "실행될 때 우선 이 라이브러리를 로딩시킬 것"이라는 메세지만을 남겨놓는다. 당연히 공유 라이브러리를 사용하면 실행화일의 크기가 작아진다. 그들은 메모리도 또한 적게 차지하며, 하드 디스크의 용량도 적게 차지한다. 리눅스의 기본 행동은 일단 공유 라이브러리가 있으면 그것과 링크를 시키고, 그렇지 않으면 정적 라이브러리를 가지고 링크 작업을 한다. 공유 라이브러리를 쓴 실행화일을 얻고자 했는데, 우연찮게 정적 실행화일이 만들어졌다면 우선 공유 라이브러리가 제대로 있는지(a.out은 *.sa, ELF는 *.so)살펴보고 읽기 퍼미션이 주어져 있는지 알아본다.

리눅스에서 정적 라이브러리는 libname.a 과 같은 식의 이름을 갖는다. 그에 비해 공유 라이브러리는 libname.so.x.y.z 라는 식의 이름을 갖는데 x.y.z는 버전을 뜻한다. 또한 공유 라이브러리는 종종 링크되어 있다. (아주 중요) libname.so.x 그리고 libname.so라는 식의 링크를 갖는다. 표준 라이브러리들은 이 둘을 모두 가지고 있다.

여러분은 ldd라는 것을 사용함으로써 특정 프로그램이 어떤 공유 라이브러리를 원하는지 알 수 있다. (ldd = List Dynamic Dependencies)

$ ldd /usr/bin/lynx
        libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
        libc.so.5 => /lib/libc.so.5.2.18

위 결과는 본인의 시스템에서 텍스트용 웹 브라우져로 사용하고 있는 lynx 라는 프로그램에 대하여 의존성 체크를 해본 결과이다. libc.so.5 (C 라이브러리)와 libncurses.so.1 (터미널 제어에 사용되는 라이브러리)를 필요로 하고 있다고 출력하고 있다. 아무런 공유 라이브러리도 필요없으면 그냥 `statically linked' 또는 `statically linked (ELF)' 라고만 출력한다.

6.2 라이브러리 들여다보기 (도대체 sin()은 어디에 들어있는가?)

nm libraryname 이라고 실행시키면 라이브러리 내의 모든 심볼을 출력해준다. 이는 공유 라이브러리와 정적 라이브러리 둘 다 적용된다. 만약 tcgetattr()이라는 함수를 찾고 싶다면 다음과 같이 해주면 된다.

$ nm libncurses.so.1 |grep tcget
         U tcgetattr

U가 뜻하는 바는 "undefined" 즉 ncurses 라이브러리가 사용하고는 있지만 아직 정의는 하지 않고 있다는 뜻이다.

이렇게도 할 수 있다.

$ nm libc.so.5 | grep tcget
00010fe8 T __tcgetattr
00010fe8 W tcgetattr
00068718 T tcgetpgrp

`W'는 "weak" 즉 심볼이 정의는 되어있으나 다른 라이브러리에 의해 재정의될 수 있는 형태라는 의미이다. 일반적으로 정상적인 경우에는 `T'라고 씌여진다.

sin()이 어디에 있는가라는 질문에 대한 가장 짧은 답은 libm.(so|a)이다. <math.h>에 정의되어 있는 모든 함수들은 바로 이 수학 라이브러리에 들어있다. 그것을 사용하기 위해서는 링크시에 -lm 옵션을 주어야 한다. using any of them.

6.3 화일 찾기

ld: Output file requires shared library `libfoo.so.1`

컴파일을 하다보면 위와 같은 메세지가 종종 나오는 것을 볼 수 있을 것이다. ld 그리고 유사한 프로그램들이 화일을 찾는 방식은 버전에 따라 다르지만 기본적으로 /usr/lib를 찾게 된다. 이 곳 말고도 다른 곳에 라이브러리를 가지고 있고 그것을 ld 에게 알려주기 위해서는 gcc 나 ld 에게 라이브러리가 잇는 디렉토리를 -L 옵션을 줘서 알린다.

-L 옵션을 주어도 안된다면, ld 가 원하는 화일이 적절한 장소에 가 있는지 확인해보라. a.out 에 대해서는 -lfoo 라고 하면 ld는 libfoo.sa (공유 라이브러리)를 찾게 된다. 만약 그것을 찾는데 실패하면 libfoo.a (정적 라이브러리)라는 화일을 찾는다. ELF에 한해서는 libfoo.so를 찾고 나서 libfoo.a를 찾는다. libfoo.solibfoo.so.x에 대한 링크이다.

6.4 여러분만의 라이브러리 만들기

버전 관리

다른 모든 프로그램과 마찬가지로 라이브러리 또한 계속적으로 버그를 잡아가야 한다. 또는 새로운 기능을 도입하거나 현재 있는 것을 더 효율적인 것으로 교체한다든지 그리고 필요없는 것은 없애버린다든지 하는 일이 필요하다. 이런 경우 변화하는 라이브러리를 가지고 프로그래밍하는 것은 문제가 아닐 수 없다. 만약 사라져버린 옛 기능에 의존하는 프로그램이라면?

그래서 우리는 라이브러리 버전이라고 하는 것을 도입한다. 그리고 라이브러리의 변화를 마이너 또는 메이저 변화 이렇게 분류하고 마이너 업그레이드는 기존의 프로그램들과 충돌이 없는 변화를 지칭하게 한다. 라이브러리의 버전은 화일명을 보면 알 수 있다. (사실 엄밀히 말하자면, ELF에 대해서는 거짓말이다. 왜 그러한지는 계속 읽어보면 나올 것이다) libfoo.so.1.2는 메이저 버전 1 이고 마이너 버전2 이다. 마이너 버전도 다소 중요한 것이 될 수도 있다. libc의 경우에는 마이너버전에다 패치레벨을 집어넣는다. 따라서 libc.so.5.2.18과 같은 이름이 생긴다. 숫자 말고도 문자, 언더스코어문자(_), 또는 프린트 가능한 문자를 넣어도 좋다.

ELF와 a.out 형식의 커다란 차이점 중에 하나가 바로 공유 라이브러리를 만드는 방식에 있다. 우선은 ELF를 알아보기로 하자. 왜냐하면 더 쉽기 때문이다.

ELF? 도대체 그게 무엇인가?

ELF (Executable and Linking Format)이라고 하는 것은 원래 USL(UNIX System Laboratories)라고 하는 곳에서 개발한 바이너리 형식이다. 그리고 현재는 솔라리스와 SVR4에서 사용 중이다. 리눅스가 사용해왔던 오래된 a.out보다 더욱 더 좋은 유연성 때문에 GCC와 C 라이브러리 개발자들은 지난 해 리눅스 표준 바이너리 형식과 마찬가지로 ELF로 이동하기로 결정하였다.

다시 한 번 더?

이번 섹션은 '/news-archives/comp.sys.sun.misc' 문서로부터 나오는 내용이다.

ELF ("Executable Linking Format)라고 하는 것은 "새롭고 향상된" 오브젝트 화일 형식으로서 SVR4 에 도입되었다. ELF는 그냥 COFF 방식보다 더욱 강력하다. 왜냐하면 사용자 확장성이 있기 때문이다. ELF는 오브젝트 화일을 임의의 길이를 갖는 섹션들의 리스트라고만 생각한다. 그것은 고정된 크기의 객체을 갖는 배열과는 다르다. 이러한 섹션은 COFF와는 달리 특정 위치에 있을 필요도 없고, 또한 특수한 순서대로 놓여있을 필요도 없다. 사용자들은 원한다면 새로운 섹션을 첨가할 수 있다. ELF는 또한 DWARF(Debugging With Attribute Record Format)라고 하는 아주 아주 강력한 디버깅 포맷을 가지고 있다. - 리눅스에서는 아직 완벽히 구현되고 있지는 않다. 하지만 작업이 진행 중이다 DWARF DIE들(또는 Debugging Information Entries) ELF 에서 .debug 섹션을 형성한다. 고정된 크기의 작은 정보들 대신에 DWARF DIE들은 각각 임의의 길이를 갖는 복잡한 속성들을 포함하고 있으며 영역별로 프로그램 데이타의 트리구조로씌여져 있다. DIE는 COFF .debug 섹션보다 많은 양의 정보를 잡아낼 수 있다.(COFF의 경우에는 C++ 계승 그래프와 같은 것들을 잡아낼 수 없다.)
ELF 화일들은 SVR4(솔라리스 2.0 ?)의 ELF 접근 라이브러리를 통해서 접근할 수 있다. 그 라이브러리는 ELF에 대하여 쉽고 빠른 인터페이스를 제공하고 있다. ELF 접근 라이브러리를 쓰면서 생기는 중요한 잇점중의 하나는 ELF 화일을 유닉스 화일로서 볼 필요가 전혀 없다는 것이다. 그것은 단지 Elf * 로서 접근가능하다. elf_open() 호출을 하면 그 다음부터 가능하다. 그 후에 elf_foobar()와 같은 작업을 한다. 이는 예전의 COFF 방식에서 실제 디스크 상의 이미지를 가지고 작업했던 것과는 전혀 다른 것이다.

ELF에 대한 찬성/반대, 그리고 현재의 a.out 시스템을 ELF 지원 시스템으로 업그레이드해야 할 필요성들은 ELF하우투 문서에서 다루고 있으며 본인은 그것을 여기에 적고자 하지는 않는다.

ELF 공유 라이브러리

libfoo.so라는 공유 라이비르러를 만들기 위한 기본적인 절차는 다음과 같다.

$ gcc -fPIC -c *.c
$ gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o
$ ln -s libfoo.so.1.0 libfoo.so.1
$ ln -s libfoo.so.1 libfoo.so
$ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH

이렇게 하면 libfoo.so.1.0이라는 공유 라이브러리가 만들어질 것이다. 그리고 ld (libfoo.so 필요)와 동적 링커(libfoo.so.1 필요)에 필요한 적절한 링크가 만들어진다. 그것을 테스트해보기 위해서 우리는 LD_LIBRARY_PATH에다 현재 디렉토리를 첨가한다.

만약 라이브러리가 제대로 작동한다는 것을 확인하면, 그 라이브러리를 /usr/local/lib로 이동시킨다. 그리고 다시 링크를 만들어준다. libfoo.so.1로부터 libfoo.so.1.0에 이르는 링크는 ldconfig라고 하는 프로그램에 의해 항상 최신 정보로 관리된다. 보통은 부팅과정에서 알아서 해준다. 하지만 libfoo.so는 수동으로 해주어야 한다. 여러분이 한번에 한 라이브러리의 모든 부분들(예를 들어 헤더화일도 해당) 꼼꼼히 업그레이드해주려고 한다면 libfoo.so -> libfoo.so.1이라는 링크를 만들어 주면 된다. 그렇게 되면 ldconfig가 알아서 링크를 관리해준다. 만약에 이런 것까지 모두 여러분 스스로 모두 행하려고 한다면 나중에 문제가 생길 수도 있다. 분명히 말해두었다.

$ su
# cp libfoo.so.1.0 /usr/local/lib
# /sbin/ldconfig
# ( cd /usr/local/lib ; ln -s libfoo.so.1 libfoo.so )

버전 번호 붙이기, soname 그리고 심볼릭 링크

각 라이브러리는 soname이라는 것을 가지고 있다. 링커가 찾고 있는 라이브러리 안에서 이러한 이름을 발견하게 되면, 실제 화일명(libfoo.so와 같은 이름)이 아니라 soname이라고 하는 것을 실행 바이너리에 표시해둔다. 실행시에는 동적 로더가 soname을 갖는 화일을 찾게 된다. 이 역시 화일명이 아니다. 이는 무엇을 의미하는가? 하면 libfoo.so 화일명을 가진 라이브러리는 libbar.so라는 soname을 가질 수도 있고 그곳에 링크된 모든 프로그램은 결국 libbar.so를 찾는다는 것이다.

이것은 상당히 무의미한 기능처럼 보이는데 사실은 이것이야말로 같은 라이브러리의 서로 다른 버전이 어떻게 한 시스템에서 공존할 수 있는가를 이해하는데 있어 핵심적인 부분이다. 리눅스에서 라이브러리 이름짓는 사실상의 표준은 라이브러리를 libfoo.so.1.2 이런 식으로 부르고 libfoo.so.1이라는 soname을 부여하는 것이다. 만약 표준 라이브러리 디렉토리(예를 들어/usr/lib)에 추가되면 ldconfig는 libfoo.so.1 -> libfoo.so.1.2라는 링크를 만들어 줄 것이다. 그렇게 함으로써 실행시에 적절한 이미지가 선택되도록 해준다. 여러분은 또한 libfoo.so -> libfoo.so.1이라는 심볼릭 링크도 필요하다. 왜냐하면 ld 가 링크할 때 정확한 soname 을 찾게 하기 위해서이다.

따라서 라이브러리의 버그를 고칠 때 또는 새로운 기능을 첨가할 때(기존의 프로그램에 악영향을 주지 않는 변화들), 다시 라이브러리를 만들고 같은 soname을 주고 화일명은 바꾸도록 한다. 만약 여러분의 라이브러리와 링크되어 있는 기존의 프로그램들과 충돌하게 되는 라이브러리로 변화할 때는 soname의 숫자를 하나 늘리면 된다. 이러한 경우 새로운 버전의 라이브러리는 libfoo.so.2.0이 될테고, soname은 libfoo.so.2가 될 것이다. 그리고 이번에는 libfoo.so를 새로운 버전의 라이브러이에 심볼릭 링크시키도록 하자.

여러분이 꼭 이런 식으로 라이브러리 이름을 지어줄 필요는 없다. 하지만 그것은 괜찮은 관습이다. ELF는 여러분에게 라이브러리 이름짓기에 있어 유연성을 주고 있지만 그렇다고 해서 꼭 그렇게만 하라는 것은 아니다.

요약하자면, 여러분이 호환성을 깨는 것이 메이저 업그레이드이고 그렇지 않은 것이 마이너 업그레이드라는 전통을 준수한다면 다음과 같이 하라.

gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor

모든 것이 제대로 될 것이다.

a.out 전통적인 형식

공유 라이브러리 만들기의 용이함은 ELF로의 업그레이드에 대한 중요한 이유이다. a.out으로 가능하기는 하다. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz를 받아오자. 그리고 그 화일을 풀어서 나오는 20 페이지짜리 문서를 읽어본다. 남들에게 뻔히 보이는 열성지지자가 되고 싶지는 않다. 하지만 나는 나 자신을 귀찮게 하고 싶지는 않다. :-)

ZMAGIC vs QMAGIC

QMAGIC 이라고 하는 것은 예전의 a.out(ZMAGIC 이라고 알려져 있다)과 마찬가지로 실행 화일의 형식이다. 하지만 첫번째 페이지는 매핑하지 않는 바이너리이다. 0-4096 까지 어떠한 매핑도 존재하지 않기 때문에 이렇게 함으로써 NULL 디레퍼런시 트래핑(deference trapping)을 아주 쉽게 할 수 있다. 부차적인 효과로서 여러분의 실행화일은 약 1K 정도 작아지게 된다.

구식 링커들은 오로지 ZMAGIC 만을 지원한다. 약간 덜 구식의 링커들은 둘 다 지원하면, 최신 버전들은 오로지 QMAGIC 만을 지원하고 있다. 이것은 별로 중요하지 않다. 왜냐하면 커널 자체가 두 가지를 모두 실행시킬 수 있기 때문이다.

file 명령을 주면 그것이 QMAGIC인지 판별할 수 있을 것이다.

화일 위치(File Placement)

a.out(DLL) 공유 라이브러리는 2 개의 실제적인 화일 그리고 하나의 링크로 구성 되어 있다. 이 문서 전체를 통해서 계속 사용해온 이름인 foo 라는 라이브러리에 대하여 예를 들어 알아보자. foo 에 대하여 libfoo.sa, libfoo.so.1.2 그리고 libfoo.so.1 이라는 링크로 구성되어 있다. 링크는 libfoo.so.1.2를 가리킨다. 이것들 모두 무엇인가?

컴파일할 때 ldlibfoo.sa를 찾는다. 이것이야말로 라이브러리에 대한 그루터기 화일이 된다. 그리고 링크과정에 대한 모든 외부 데이타와 함수에 대한 포인터를 지니고 있다.

하지만 실행시에는 동적 로더가 libfoo.so.1을 찾는다. 이는 실제 화일이 아니라 심볼릭 링크이다. 그 이유는 앞서와 마찬가지로 라이브러리가 기존의 어플리케이션과의 충돌없이, 더 새로운, 버그가 잡힌 새로운 버전으로 교체될 수 있도록 하기 위해서이다. 새로운 버전이 나오면(예를 들어 libfoo.so.1.3)이라고 하자. ldconfig를 실행시키면 자동으로 libfoo.so.1 --> libfoo.so.1.3 링크 작업을 해 줄 것이다. 구버전을 쓰는 프로그램도 아무 이상이 없을 것이다.

DLL 라이브러리(동어반복이라는 사실은 알고 있다. 역자 주 :DLL 에 이미 라이브러리라는 말이 들어있다)는 종종 정적 라이브러리보다 크다. DLL은 미래의 확장성을 위해서 뻥 뚤린 구멍의 형태로 자리를 유보해둔다. 하지만 그 자리는 디스크 영역을 차지하지는 않도록 할 수 있다. 간단한 cpmakehole이라는 프로그램으로 이렇게 하는 것이 가능하다. 이미 고정된 위치에 주소들이 있으므로 라이브러리 생성 후에 strip 할 수 있다. 하지만 ELF 라이브러리에 대해서는 strip하지 말라.

libc-lite 란 무엇인가?

libc-lite 라고 하는 것은 libc 에 대한 소규모 버전이라고 할 수 있다. 하나의 플로피 안에 들어가고 유닉스의 자잘한 많은 업무들에 충분한 정도만으로 구성된 라이브러리이다. 그것은 curses 나 dbm, termcap 등의 코드를 포함하고 있지 않다. 만약 여러분의 /lib/libc.so.4가 lite 버전의 라이브러리에 링크되어 있다면 즉시 완전한 libc 버전으로 교체하기 바란다.

보통 슬랙웨어의 루트 디스켓을 마운트해보면 이 lite 버전의 C 라이브러리가 들어있음을 알 수 있을 것이다. 설치 준비와 설치에 필요한 만큼의 작은 C 라이브러리이다.

링크하기 : 일반적인 문제들

여러분의 링크 문제를 내게 보내달라! 그러면 그것에 대해서 나는 아무 일도 하지 않을 것이다. 하지만 많이 쌓이는 문제에 대해서는 글을 쓰겠다.

공유 라이브러리와 링크되길 바라는데 정적 라이브러리와 링크되고 있다.

우선은 ld가 공유라이브러리를 제대로 찾을 수 있도록 링크가 알맞게 되어 있는지 점검한다. ELF에 대해서라면 이것은 libfoo.so 심볼릭 링크를 말하며 a.out의 경우에는 libfoo.sa화일을 말하는 것이다. ELF binutil 2.5 버전에서 2.6 버전으로 업그레이드한 많은 사람들이 겪고 있는 문제이다. 전 버전이 공유 라이브러리에 대하여 오히려 더 똑똑하게 찾아냈는데, 그 사람들은 모든 링크를 제대로 만들지 않았던 것이다. 지적인 행동양식을 다른 모든 설계방식과의 호환성을 위해서 신버전에서 제거되었다. 지적 행동양식은 잘못된 가정을 갖게 되고 오히려 더 많은 문제를 낳기 때문에 그렇게 한 것이다.

DLL 툴인 mkimage 가 libgcc를 찾는데 실패한다.

libc.so.4.5.x와 그 이상의 버전에 관하여 libgcc는 더 이상 공유 라이브러리가 아니다. 따라서 여러분은 `-lgcc'와 같은 라인을 모두 `gcc -print-libgcc-file-name`로 바꿔주어야 한다. (주의할 것은 바로 백쿼우트문자(`)의 사용이다. 꼭 이 문자만을 사용하라.)

또한 모든 /usr/lib/libgcc* 화일들을 삭제하라. 이것이 중요하다.

__NEEDS_SHRLIB_libc_4도 마찬가지 문제이다.
DLL 생성시에 ``Assertion failure'' 메시지

이 메시지는 여러분이 가지고 있는 jump table 슬롯이 원래의 jump.vars화일에 너무 적은 공간 밖에 예약되지 않았기 때문에 오버플로우로 인해 생기는 문제이다. 여러분은 tools-2.17.tar.gz 패키지에 들어 있는 `getsize' 명령을 사용하여 그 범인을 찾아낼 수 있다. 아마도 유일한 해결책은 메이저 번호의 증가 밖에 없는 것 같다. 단지 이전 버전과 호환되도록 고려하면서 말이다.

ld: output file needs shared library libc.so.4

이러한 문구는 보통 libc가 아닌 라이브러리들 (즉, X 윈도우 라이브러리들...)하고 링크하려고 할 때 발생한다. -static을 함께 사용하지 않고 링크 시에 -g 옵션을 주었을 때이다.

공유 라이브러리에 대한 .sa 화일은 보통 정의되지 _NEEDS_SHRLIB_libc_4 라는 심볼을 가지고 있는데 나중에 libc.sa에서 해결된다. 하지만 -g 옵션을 주게 되면 libg.a 또는 libc.a와 링크되게 되므로 그 심볼은 해결이 되지 않게 되고 위와 같은 에러 메세지가 뜨게 되는 것이다.

결론적으로 -g 플래그로 컴파일할 때는 -static 이라는 옵션을 함께 주기 바란다. 또는 -g로 컴파일하지 않으면 된다. 링크할 것 없이 원하는 부분만 -g 옵션을 주고 컴파일해도 충분한 디버깅 정보를 얻을 수 있다.

7. 동적 로딩(Dynamic Loading)

이번 섹션은 지금 현재로선 아주 적은 내용만을 가지고 있다. ELF 하우투 문서를 발췌함으로써 그 내용이 계속적으로 늘어나게 될 것이다.

7.1 개념 잡기

리눅스는 공유 라이브러리를 가지고 있다. 이 글 전체를 읽는 동안 이제는 이런 말 듣는 것도 질렸을 것이다. 전통적으로 프로그램 링크 과정에서 행한 작업은 로딩 과정에서 그 반대 과정을 거쳐야 한다.

7.2 에러 메세지

can't load library: /lib/libxxx.so, Incompatible version

a.out 에서만 일어나는데, 이 말은 여러분의 라이브러리 메이저 버전이 틀리다는 말이다. 다른 버전을 가지고 있다고 해서 눈가림식으로 심볼릭 링크하는 것으로 안된다. 된다 할지라도 결국엔 세그폴트를 일으킬 것이다. 새로운 버전을 가져오라.ELF에서도 비스한 메세지가 나온다.

ftp: can't load library 'libreadline.so.2'
warning using incompatible library version xxx

a.out의 경우이다. 프로그램 컴파일한 사람보다 낮은 마이너 버전의 라이브러리를 갖고 있기 때문에 발생하는 경고 메세지이다. 프로그램이 실행되기는 할 것이다. 업그레이드하는 것이 어떨까?

7.3 동적 로더의 작동 제어하기

많은 환경 변수들이 동적 로더에 관계한다. 대부분은 일반 사용자보다는 ldd에게 유용하다. ldd에 다양한 스위치를 줌으로써 쉽게 세팅할 수 있다.

  • LD_BIND_NOW --- 일반적으로 함수가 호출되기 전까지는 라이브러리에서 찾아보지 않는다. 이 플래그를 세팅해주면 라이브러리 적재시에 모든 체크를 하게 되고 시작은 상당히 느리게 된다. 이것은 여러분이 만든 프로그램이 모든 것들과 제대로 링크가 되었는지 시험해볼 때 유용하다.
  • LD_PRELOAD --- overriding 함수 정의를 가지고 있는 화일에 세팅될 수 있다. 예를 들어서 메모리 할당 방법을 테스팅하려고 하며, malloc를 교체하려고 할 때는 여러분이 원하는 루틴으로 만든 후에 교체할 수가 있다. malloc.o 라는 이름으로 컴파일한 후 다음과 같이 해보자.
    $ LD_PRELOAD=malloc.o; export LD_PRELOAD
    $ some_test_program
    
    LD_ELF_PRELOADLD_AOUT_PRELOAD 이 둘은 비슷하다. 하지만 각각 특정 형태에만 관계한다. 만약 LD_ELF_PRELOADLD_PRELOAD가 둘 다 사용되었다면 좀 더 자세히 지정한 전자 LD_ELF_PRELOAD가 사용된다.
  • LD_LIBRARY_PATH --- 이것은 공유 라이브러리를 찾을 때 참고할 디렉토리를 콜론(:)을 분리자로 써서 표현한 리스트이다. 그것은 ld에 영향을 주지는 못한다. 단지 실행시에만 관계한다. 또한 setuid나 setgid를 갖는 프로그램에 대해서는 무용지물이다. 마찬가지로 LD_ELF_LIBRARY_PATHLD_AOUT_LIBRARY_PATH는 각각의 바이너리 형식에만 적용되도록 하고 있다. LD_LIBRARY_PATH는 정상적인 경우 그렇게 필요하진 않다. 대신에 /etc/ld.so.conf/에 디렉토리를 추가하고 ldconfig를 다시 한 번 실행시키는게 좋다.
  • LD_NOWARN --- 이는 a.out에만 적용된다. 예를 들어 다음과 같이 세팅하면 LD_NOWARN=true; export LD_NOWARN) 마이너 버전이 다르다든지 하는, 크게 심각하지 않는 경고를 표시하지 않도록 한다.
  • LD_WARN --- 이는 ELF에만 해당된다. 세팅되면 일반적으로 ``Can't find library''와 같은 심각한 에러를 경고로 바꾸어준다. 별로 필요없는 옵션이다.
  • LD_TRACE_LOADED_OBJECTS --- ELF에만 적용된다. 프로그램으로 하여금 ldd 하에서 실행되고 있다고 생각하게끔 만든다.
    $ LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx
            libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
            libc.so.5 => /lib/libc.so.5.2.18
    

7.4 동적 로딩을 사용하는 프로그램 만들기

이는 솔라리스 2.x의 동적 로딩 지원이 이뤄지는 방식과 매우 흡사하다. H J Lu의 ELF 프로그래밍 문서에 자세히 나와 있으며 dlopen(3) 맨페이지에 아주 잘 나와 있다. 맨페이지는 ld.so 패키지에 들어있다. 다음 프로그램을 -ldl 옵션을 주고 링크하라.

#include <dlfcn.h>
#include <stdio.h>

main()
{
  void *libc;
  void (*printf_call)();

  if(libc=dlopen("/lib/libc.so.5",RTLD_LAZY))
  {
    printf_call=dlsym(libc,"printf");
    (*printf_call)("hello, world\n");
  }

}

8. 개발자와 연락하기(이하는 번역되어 있지 않습니다.)

8.1 Bug reports

Start by narrowing the problem down. Is it specific to Linux, or does it happen with gcc on other systems? Is it specific to the kernel version? Library version? Does it go away if you link static? Can you trim the program down to something short that demonstrates the bug?

Having done that, you'll know what program(s) the bug is in. For GCC, the bug reporting procedure is explained in the info file. For ld.so or the C or maths libraries, send mail to linux-gcc@vger.rutgers.edu. If possible, include a short and self-contained program that exhibits the bug, and a description both of what you want it to do, and what it actually does.

8.2 Helping with development

If you want to help with the development effort for GCC or the C library, the first thing to do is join the linux-gcc@vger.rutgers.edu mailing list. If you just want to see what the discussion is about, there are list archives at http://homer.ncm.com/linux-gcc/. The second and subsequent things depend on what you want to do!

9. The Remains

9.1 The Credits

Only presidents, editors, and people with tapeworms have the right to use the editorial ``we''.
(Mark Twain)

This HOWTO is based very closely on Mitchum DSouza's GCC-FAQ; most of the information (not to mention a reasonable amount of the text) in it comes directly from that document. Instances of the first person pronoun in this HOWTO could refer to either of us; generally the ones that say ``I have not tested this; don't blame me if it toasts your hard disk/system/spouse'' apply to both of us.

Contributors to this document have included (in ASCII ordering by first name) Andrew Tefft, Axel Boldt, Bill Metzenthen, Bruce Evans, Bruno Haible, Daniel Barlow, Daniel Quinlan, David Engel, Dirk Hohndel, Eric Youngdale, Fergus Henderson, H.J. Lu, Jens Schweikhardt, Kai Petzke, Michael Meissner, Mitchum DSouza, Olaf Flebbe, Paul Gortmaker, Rik Faith, Steven S. Dick, Tuomas J Lukka, and of course Linus Torvalds, without whom the whole exercise would have been pointless, let alone impossible :-)

Please do not feel offended if your name has not appeared here and you have contributed to this document (either as HOWTO or as FAQ). Email me and I will rectify it.

9.2 Translations

At this time, there are no known translations of this work. If you wish to produce one, please go right ahead, but do tell me about it! The chances are (sadly) several hundred to one against that I speak the language you wish to translate to, but that aside I am happy to help in whatever way I can.

dan@detached.demon.co.uk. My PGP public key (ID 5F263625) is available from my web pages, if you feel the need to be secretive about things.

9.4 Legalese

All trademarks used in this document are acknowledged as being owned by their respective owners.

This document is copyright (C) 1996 Daniel Barlow <dan@detached.demon.co.uk> It may be reproduced and distributed in whole or in part, in any medium physical or electronic, as long as this copyright notice is retained on all copies. Commercial redistribution is allowed and encouraged; however, the author would like to be notified of any such distributions.

All translations, derivative works, or aggregate works incorporating any Linux HOWTO documents must be covered under this copyright notice. That is, you may not produce a derivative work from a HOWTO and impose additional restrictions on its distribution. Exceptions to these rules may be granted under certain conditions; please contact the Linux HOWTO coordinator at the address given below.

In short, we wish to promote dissemination of this information through as many channels as possible. However, we do wish to retain copyright on the HOWTO documents, and would like to be notified of any plans to redistribute the HOWTOs.

If you have questions, please contact Tim Bynum, the Linux HOWTO coordinator, at linux-howto@sunsite.unc.edu via email.

10. Index

Entries starting with a non-alphabetical character are listed in ASCII order.

  • -fwritable-strings 39 56
  • /lib/cpp 16
  • a.out 1
  • ar 10
  • as 8
  • <asm/*.h> 19
  • atoi() 40
  • atol() 41
  • binaries too big 63 65 77
  • chewing gum 3
  • cos() 68
  • debugging 59
  • dlopen() 82
  • dlsym() 83
  • documentation 4
  • EINTR 52
  • elf 0 71
  • execl() 57
  • fcntl 47
  • FD_CLR 44
  • FD_ISSET 45
  • FD_SET 43
  • FD_ZERO 46
  • file 2
  • <float.h> 20
  • gcc 6
  • gcc -fomit-frame-pointer 61
  • gcc -g 60
  • gcc -v 14
  • gcc, bugs 15 28 29 84
  • gcc, flags 13 25 26
  • gdb 64
  • header files 17
  • interrupted system calls 51
  • ld 9
  • LD_* environment variables 80
  • ldd 81
  • libc 7
  • libg.a 62
  • libgcc 79
  • <limits.h> 21
  • lint 58
  • <linux/*.h> 18
  • manual pages 5
  • <math.h> 70
  • maths 69
  • mktemp() 55
  • optimisation 27
  • QMAGIC 76
  • segmentation fault 30 54
  • segmentation fault, in GCC 33
  • select() 50
  • SIGBUS 34
  • SIGEMT 35
  • SIGIOT 36
  • SIGSEGV 31 53
  • SIGSEGV, in gcc 32
  • SIGSYS 38
  • SIGTRAP 37
  • sin() 67
  • soname 73
  • sprintf() 42
  • statically linked binaries, unexpected 66 78
  • <stdarg.h> 23
  • <stddef.h> 24
  • strings 11
  • <sys/time.h> 48
  • <unistd.h> 49
  • <varargs.h> 22
  • version numbers 12 74
  • weird things 72
  • ZMAGIC 75
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.
TAGS ,

Leave your greetings here.

  
  
  
  
  
  
  
  
 

gcc와 make

2008/01/07 11:21 / Resource
참고문헌

Running LINUX(Matt Welsh, Lar Kaufman), "오렐리 출판사"

1. 시작하면서

2. gcc 강좌

3. make 강좌


1. 시작하면서

1.1 C 와 gcc 와의 관계

세상에서 제일 뛰어난 C 컴파일러 중 하나인 gcc 는 리눅스나 기타 자유 운영체제에 있어 커다란 보배가 아닐 수 없습니다. 우리가 알고 있는 유닉스가 C 언어로 거의 다 만들어졌듯이 리눅스의 모국어는 바로 gcc 입니다.

사실 많은 분들이 리눅스 해커(hacker), 구루(guru)의 경지가 되고 싶어합니다. 그렇게 되길 원하신다면 리눅스용의 모국어인 gcc 를 익히십시요. gcc 를 알면 리눅스를 아는 것이나 다름 없습니다. 사실 C 와 유닉스가 따로 떨어진 것이 아니라 어떻게 보면 일심동체라고 할 수도 있듯이 gcc 와 리눅스는 일심동체라고 봐도 무방합니다.

C 언어! 이는 유닉스와 심지어 마이크로소프트 제품에 이르기까지(어떤 식으로 변질되었든 간에 ) 컴퓨터 세상의 ``만국 공통어''입니다. 여태까지 이러한 언어의 통일을 이뤄낸 것은 C 언어밖에 없습니다. 컴퓨터 언어의 에스페란토어를 지향하는 많은 언어들( 자바, 티클/티케이 )이 나오고 있으나 이는 두고 볼 일입니다. 그리고 그 언어를 구사한다 할 지라도 C 언어는 역시나 ``기초 교양 언어''입니다.

여러분은 리눅스에서 gcc 를 통하여 그 동안 도스/윈도 환경에서 배운 엉터리 C 언어를 잊으셔야 합니다. 감히 말하건데 그것은 C 언어가 아닙니다. C 언어는 만국 공통어야 함에도 불구하고 몇몇 회사들, 도스/윈도와 같은 환경에서 변질되어 각 환경마다 ``새로운 문법''을 배워야 하는 어처구니없는 사태가 벌어졌습니다. 터보 C 와 MS-C를 배우면서 혼란도 많았고 그 뒤에 나온 녀석들은 완전히 다른 놈들이나 다름 없습니다.

지금 리눅스에서 여러분은 C 언어의 ``정통 소림권법''을 익히실 수 있습니다. 기초가 없이 비법만 전수받아 보았자 다른 곳에 가면 수많은 비법을 지닌 무림고수들에게 여지없이 깨지기 마련입니다. 하지만 아무리 괴로와도 처음에 물 길어오는 것, 마당 쓰는 일부터 시작하면 철통같은 신체를 단련하기 때문에 온갖 꽁수 비법으로는 여러분을 헤칠 수 없습니다. 또한 정통 권법을 연마한 사람은 기본기가 갖춰져 있으므로 대련 중에도 상대의 비법을 금방 간파하고 심지어 상대의 비법만마저 자신의 것으로 하기도 합니다. ^^


1.2 gcc 에 대한 이야기 하나

gcc 는 GNU 프로젝트에 의해 만들어진 작품의 하나로서 그 명성은 하늘을 찌를 듯합니다. GNU 프로젝트응의 산물 중 가장 멋진 것을 꼽으라면 저는 주저하지 않고 C 컴파일러의 최고봉인 gcc 를 지목할 것입니다.

실제로 gcc 의 명성은 뛰어나며 수많은 상용 회사도 스폰서를 해주고 있다는 것을 아시는지요? 예를 들어 넥스트사( 지금은 사라짐 )의 새로운 C 언어인 ``오브젝티브 C''는 gcc 를 가져다 만든 것이며 FSF 측에 다시 기증 되었습니다.

gcc 는 아주 강력합니다! 이미 상용 유닉스에 달려오는 AT&T 스타일, BSD 스타일의 C 언어 문법은 물론 ANSI C 를 기본으로 하여 모든 문법을 소화해낼 수 있으며 특유의 문법도 가지고 있습니다. 아주 구식 컴파일러, 아주 구식 문법도 소화해낼 수 있습니다. 이미 많은 사람들이 상용 유닉스에도 gcc 를 설컴치하여 사용하는 경우가 많지요. ( 물론 금전적인 문제가 많이 작용 ^^ )

gcc 는 매우 단순합니다. 어떤 의미로 이런 말을 하는가 하면, 터보 C/볼랜드 C 통합환경이나 윈도 환경의 비주얼한 환경을 제공하지 않는다는 것입니다. -.- 그들이 상당히 오토매틱한 성격을 갖는 반면, gcc 는 오로지 수동 스틱방식입니다. 각각의 장단점이 있지만 여러분이 일단 gcc 를 만나시려면 각오는 하고 계셔야 합니다. 도스/윈도에서 보던 것을 원하지 마십시요. gcc 는 껍데기에 신경쓸 겨를조차 없습니다. gcc 는 오로지 명령행 방식만을 제공합니다. 그리고 그 자체로 파워풀합니다. 개발 방향은 계속 ``뛰어난 능력''이지 겉모양 화장은 아닐 것입니다. ( 만약 겉모양을 원하신다면 그것은 여러분의 몫입니다. xwpe 같은 것이 그 좋은 예라고 할 수 있습니다 )

gcc 는 어떻게 보면 C 언어에 대한 개념이 서지 않는 사람에게는 무리인 C 컴파일러인 듯 합니다. 기초 지식없이 사용한다는 것은 불가능에 가깝습니다. 하지만 C 언어를 확실하게 기초부터 배워서 어디서든 쓰러지지 않는 무림고수가 되기 위해서는 gcc 를 권합니다. 자잘한 무공을 하는 깡패가 되느냐? 아니면 정신을 지닌 무림고수가 되느냐?는 여러분의 선택에 달렸습니다.

gcc 가 어렵기만 한가? 하면 그렇지는 않습니다. gcc 는 상당한 매력을 지니고 있습니다. 그 매력으로 인해 한 번 빠지면 다른 컴파일러가 상당히 우습게 보이기까지 합니다. ( 그렇다고 다른 컴파일러를 비웃지는 마세요 ^^ 그거 쓰는 사람들이 자존심 상해서 엄청 화를 낼 테니까요. 개인적으로 생각하기에 gcc 에 대적할 수 있을 정도되는 컴파일러는 와콤 C 컴파일러 정도? )

gcc 를 배우시려면 정신 무장(?)이 중요하다고 생각해서 이렇게 장황하게 읊었습니다. 심플하게 배우면서 여러분의 리눅스, C 컴파일러에 대한 두려움을 하나씩 없애고 C 언어 위에 군림하시기 바랍니다.

자, 이제는 잡담없이 시작합니다.

2. gcc 강좌

2.1 gcc 에 대한 기본 이해

명령행 상태에서 다음과 같이 입력해봅시다. 여러분이 사용하같고 있는 gcc 버전은 알아두고 시작하셔야겠죠?

 [yong@redyong yong]$ gcc -v
 Reading specs from /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs
 gcc version 2.7.2.1
 [yong@redyong yong]$ 

gcc -v 이라고 입력하니까 ``Reading specs from..'' 이라같고 말하면서 그 결과값을 ``gcc version 2.7.2.1''이라고 말해주고 있습니다. 자, 어디서 gcc 에 대한 정보를 읽어오는지 봅시다.

  /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs

gcc 를 여러분이 소스를 가져다 손수 설치해보신 적은 없을 것입니다. 보통은 바이너리 패키지로 된 것을 가져다 설치하지요. 나중에 정말 휴일에 너무 심심하다 싶으면 gcc 의 소스를 가져와서 컴파일해보십시요. 참, 재미있는 경험이 될 것입니다. 이미 여러분이 갖고 있는 gcc 를 가지고 새로운 gcc 를 컴파일하여 사용합니다. C 컴파일러를 가지고 새 버전의 C 컴파일러를 컴파일하여 사용한다! 이런 재미있는 경험을 또 어디서 해보겠습니까?

gcc 패키지가 어떤 것으로 구성되어 있는지.. gcc 가 제대로 설치되어 있는지 알아보면 좋겠죠?

다음과 같습니다.

 /lib/cpp       -----------> /usr/lib/gcc-lib/i386-linux/2.7.2.1/cpp ( 링크임 )
 /usr/bin/cc    -----------> gcc ( 링크임 )
 /usr/bin/gcc                C 컴파일러 ``front-end''
 /usr/bin/protoize
 /usr/bin/unprotoize
 /usr/info/cpp.info-*.gz     GNU info 시스템을 이용하는 화일들
 /usr/info/gcc.info-*.gz                        
 /usr/lib/gcc-lib

마지막 /usr/lib/gcc-lib 디렉토리에 아래에 gcc 에 관한 모든 내용이 설치됩니다.

보통 다음과 같은 디렉토리 구조를 가집니다.

        /usr/lib/gcc-lib/<플랫폼>/< gcc 버전 >

보통 우리는 리눅스를 i386 ( 인텔 환경 )에서 사용하고 있으므로 다음과 같이 나타날 것입니다.

        /usr/lib/gcc-lib/i386-linux/2.7.2.1

( i386-linux, i486-linux, i586-linux 는 각기 다를 수 있습니다. 하지만 상관없는 내용입니다. 미친 척 하고 다른 이름을 부여할 수도 있습니다. )

그럼 계속 해서 /usr/lib/gcc-lib 밑의 내용을 살펴보죠.

 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cpp
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/include/*.h
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/libgcc.a
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/specs

cc1이 진짜 C 컴파일러 본체입니다. gcc 는 단지 적절하게 C 인가, C++ 인가 아니면 오브젝티브 C 인가를 검사하고 컴파일 작업만이 아니라 ``링크''라는 작업까지 하여 C 언어로 프로그램 소스를 만든 다음, 프로그램 바이너리가 만들어지기까지의 모든 과정을 관장해주는 ``조정자'' 역할을 할 뿐입니다.

C 컴파일러는 cc1, C++ 컴파일러는 cc1plus, 오브젝티브 C 컴파일러는 cc1obj 입니다. 여러분이 C++/오브젝티브 C 컴파일러를 설치하셨다면 cc1plus, cc1obj 라는 실행화일도 찾아보실 수 있을 겁니다. cpp 는 "프리프로세서"입니다. #include 등의 문장을 본격적인 cc1 컴파일에 들어 가기에 앞서 먼저(pre) 처리(process)해주는 녀석입니다.

참고로 g++ 즉 C++ 컴파일러( 정확히는 C++ 컴파일러 프론트 엔드 )에 대한 패키지는 다음과 같습니다.

 /usr/bin/c++   --------------------------->    g++ 에 대한 링크에 불과함
 /usr/bin/g++
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1plus    ( 진짜 C++ 컴파일러 )

오브젝티브 C 컴파일러 패키지는 다음과 같습니다.

 /usr/lib/gcc-lib/i386-linux/2.7.2.1/cc1obj
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/include/objc/*.h
 /usr/lib/gcc-lib/i386-linux/2.7.2.1/libobjc.a

구성요소가 어떤 것인지 아셨으니 좀 도움이 되셨을 겁니다.

2.2 gcc 사용하기

hello.c 라는 지긋지긋한 소스 하나를 기준으로 설명합니다 ^^


#include <stdio.h>

int
main ( void )
{
  (void) printf ( "Hello, Linux Girls! =)\n" );
  return 0;
}

참고로 제일 간단한 소스는 다음과 같은 것입니다. ^^


main () {}

컴파일을 해보겠습니다! $ 는 프롬프트이지 입력하는 것이 아닌 것 아시죠?

 $ gcc hello.c
 $

무소식이 희소식이라... gcc <C소스 화일명> 이렇게 실행하고 나서 아무런 메시지도 나오지 않고 다음 줄에 프롬프트만 달랑 떨어지면 그것이 바로 컴파일 성공입니다.

여러분은 무심코 다음과 같이 결과 프로그램을 실행시키려 할 것입니다.

 $ hello
 bash: hello: command not found
 $

예. 땡입니다. ^^

여러분은 다음과 같이 실행시켜야 합니다.

 $ ./a.out

맨 앞의 도트 문자(.)는 현재 디렉토리를 의미합니다. 그 다음 디렉토리 구분 문자 슬래쉬(/)를 쓰고 유닉스 C 에서 ``약속한'' C 컴파일러의 출력 결과 바이너리 화일인 a.out 을 써줍니다.

이러한 습관은 아주 중요합니다. 여러분이 현재 디렉토리에 어떤 실행 화일을 만들고 나서 테스트를 해 보려고 한다면 꼭 ./<실행 화일명> 이라고 적어줍니다.

유닉스는 기본적으로 PATH 라는 환경변수에 있는 디렉토리에서만 실행화일을 찾을 뿐입니다. 만약 PATH 라는 환경변수에 현재 디렉토리를 의미하는 도트 문자(.)가 들어있지 않으면 현재 디렉토리의 실행화일은 절대 실행되지 않습니다. 게다가 현재 디렉토리를 PATH 환경 변수에 넣어준다 할 지라도 도스처렁럼 현재 디렉토리를 먼저 찾는다든지 하는 일은 없습니다. 오로지 PATH 에 지정한 순서대로 수행합니다.

실행 바이너리명이 계속 a.out 으로 나오면 좀 곤란하죠. 뭐 물론 mv 명령으로 a.out 의 이름을 바꾸면 되지만서도...

-o 옵션

-o 옵션( 소문자 o 임! )은 출력(output) 화일명을 정하는 옵션입니다. 위에서 우리는 hello.c 라는 소스를 가지고 일반적으로 hello 라는 이름의 실행화일을 만들고 싶어할 것입니다.

 $ gcc -o hello hello.c
       ^^^^^^^^

또는 다음과 같이 순서를 바꿔도 무방합니다.

 $ gcc hello.c -o hello
               ^^^^^^^^

워낙 유닉스 쪽은 명령행 방식이 전통적으로 주된 방식이라 명령행에서 하는 일은 뛰어납니다.

당연히 실행을 하려면 ./hello 라고 하셔야 합니다. 결과는 다음처럼 나오겠지요?

 $ ./hello
 Hello, Linux Girls! =)
 $

주의

제일 안좋은 습관 중 하나가 바로 테스트용으로 만든 소스라고 다음처럼 하는 것입니다.

 $ gcc -o test test.c
 $ test
 $

문제를 알아내기 위하여 위에서 작성한 hello.c 를 컴파일/링크해봅시다.

 $ gcc -o test hello.c
 $ test
 $

원하는 문자열이 출력되지 않았습니다. -.-

 $ ./test
 Hello, Linux Girls! =)
 $

-c 옵션

어떤 이유로 오로지 컴파일(compile) 작업만 하고 싶은 경우가 있습니다. 그럴 때는 다음과 같이 합니다.

 $ gcc -c hello.c
 $

그 결과 만들어지는 화일은 전통적으로 hello.c 에서 .c 부분을 떼어내고 .o 를 붙인 화일입니다. 오브젝트 화일, 목적 화일이라고 하지요.

hello.o 라는 화일이 만들어집니다.

여러분은 C 언어로 조금이라도 복잡한 프로그램을 만들기 시작하면 여러 개의 소스로 나누어서 전체 프로그램을 짜게 됩니다. 그럴 때는 각 소스가 전체 기능에 기여하는 특정 기능의 함수들을 가지게 되고 오로지 한 녀석만 main 함수를 가집니다.

만약 어떤 프로그램이 foo.c, bar.c 이렇게 두 개의 소스로 이루어져 있다고 합시다. 이럴 때는 다음과 같이 하는 것이 가능합니다.

방법(1)

 $ gcc -o baz foo.c bar.c
 $ ./baz
방법(2)

 $ gcc -c foo.c
 $ gcc -c bar.c

          또는
 
 $ gcc -c foo.c bar.c
 $ gcc -o baz foo.o bar.o
              ^^^^^^^^^^^
 $ ./baz

위에서 보면 "아니! C 컴파일러에 .c 즉 소스 화일이 아닌 오브젝트 화일도 막 써주나?"라는 생각을 하시게 될 겁니다.

그렇습니다! 왜냐? gcc 는 정확히 말해서 C 컴파일러가 아닙니다. gcc 라는 실행 화일 자체는 "C 컴파일러를 돌리는 녀석"입니다.

더욱 더 정확히 말해보겠습니다.

C 언어는 기본적으로 두 가지 과정을 거쳐야만 실행화일을 만들어냅니다.

  1. 컴파일 ( .c -------> .o )
  2. 링크 ( .o -------> 실행화일 a.out )

1. 과정을 실제로 맡는 것은 cc1 이라는 녀석이고 2. 과정을 맡는 것은 ld 라는 링커(linker)입니다.

gcc 는 상당히 편리한 도구로서 .c, .o 등의 화일명 꼬리말을 보고 적절하게 C 컴파일러와 링커를 불러다가 원하는 실행화일을 만들어줍니다. gcc 는 "컴파일러와 링커를 불러주는 대리인"입니다.

hello.c 를 괜히 어렵게 컴파일/링크해봅시다 ^^

 $ gcc -c hello.c
          ^^^^^^^
 $ gcc -o hello hello.o
                ^^^^^^^

gcc 가 얼마나 똑똑피한 놈인지 알아보죠.

 $ gcc -c hello.o

이게 무슨 의미가 있겠습니까? ^^

 gcc: hello.o: linker input file unused since linking not done

위와 같이 불평합니다. 링크 과정을 수행하지 않으므로 링커에 대한 입력 화일인 hello.o 를 사용하지 않았다!

-I 옵션

#include 문장에서 지정한 헤더 화일이 들어있는 곳을 정하는 옵션입니다. 아주 많이 사용되는 옵션 중 하나입니다.


 #include <stdio.h>
 #include "my_header.h"

전자( <> 문자를 쓴 경우 )는 시스템 표준 헤더 디렉토리인 /usr/include를 기준으로 화일을 찾아서 포함시킵니다. 표준 디렉토리이지요.

후자( "" 문자를 쓴 경우 )는 지금 컴파일러가 실행되고 있는 현재 디렉토리를 기준으로 헤더 화일을 찾습니다.

이 두 디렉토리가 아닌 곳에 대해서는 명시적으로 -I<디렉토리> 로 정해줍니다.

 $ gcc -c myprog1.c -I..
 $ gcc -c myprog1.c -Iinclude

첫번째는 헤더 화일이 현재 소스 하위 디렉토리(..)에 있다는 뜻이며 두번째는 현재 디렉토리의 include라는 디렉토리에 들어있다는 뜻입니다.

-I 옵션은 얼마든지 여러번 쓸 수 있으며 주어진 순서대로 헤더 화일을 검색합니다.

주의

디렉토리명은 -I 라는 문자 바로 다음에 붙여서 씁니다. 즉 -I <디렉토리>라는 식이 아니라 -I<디렉토리> 입니다. 또한 유닉스에 있어 표준 헤더 화일 디렉토리는 /usr/include 라는 사실을 기억하시기 바랍니다. 또한 리눅스에 있어서는 커널 소스가 아주 중요한데 리눅스 고유의 기능을 쓰는 리눅스용 프로그램의 경우에는 /usr/include/linux, /usr/include/asm, /usr/include/scsi (최신 커널의 경우) 라는 디렉토리가 꼭 있어야 하며 각각은 커널 소스의 헤더 디렉토리에 대한 링크입니다. 따라서 커널 소스를 꼭 설치해두셔야 합니다.

 /usr/include/linux   -------------->  /usr/src/linux/include/linux
 /usr/include/asm     -------------->  /usr/src/linux/include/asm  
 /usr/include/scsi    -------------->  /usr/src/linux/include/scsi

( 위에서 /usr/src/linux/include/asm은 사실 대부분의 경우 /usr/src/linux/include/asm-i386 이라는 디렉토리에 대한 링크입니다 )

각각 linux는 일반적인 C 헤더 화일, asm은 각 아키텍쳐별 의존적인 어셈블리 헤더 화일, 맨 마지막은 SCSI 장치 프로그래밍에 쓰이는 헤더 화일이 들어있는 곳입니다.

일반적으로 커널 소스( 약 6 메가 이상되는 소스 )는 /usr/src 에서 tar, gzip으로 풀어두는 것이 관례입니다.

맨 처음 프로그래밍을 할 때는 그렇게 많이 쓰지는 않는 옵션이지만 여러분이 다른 소스를 가져다 컴파일할 때 아주 많이 보게 되는 옵션이므로 일단 이해는 할 수 있어야겠죠?

-l 옵션과 -L 옵션

옵션을 제대로 이해하기에 앞서 ``라이브러리''라는 것에 대한 이야기를 먼 저 하지 않으면 안될 듯 하군요.

  • 라이브러리


       ``라이브러리(Library)''라는 것은 개념상 영어 단어 그대로입니다.
      무엇인가 유용한 지식을 한 곳에 모아둔 곳이라는 개념이지요.

       C 프로그래밍을 하다 보면 반복적으로 사용하게 되는 함수들이 있기
      마련이고 그것은 하나의 함수로 떼내어 어디에서든 유용하게 사용할
      수 있도록 합니다.

       이 함수가 극도로 많이 사용되는 경우에는 ``라이브러리''라는 것으
      로 만들어두고 매번 컴파일해둘 필요없이 가져다 사용할 수 있도록
      하지요.

       라이브러리에 대한 얘기는 다음 번에 또 하게 되겠지만 일단 지금
      필요한 지식만 쌓기로 하겠습니다.

       일반적으로 관례상 라이브러리는 화일명 끝이 .a 로 끝납니다.
      여기서 a 는 Archive 라는 의미일 것입니다.

       라이브러리의 예를 들어보도록 하죠. 지금 /usr/lib 디렉토리를 한
      번 구경해보십시요. 정말로 많은 라이브러리들이 있지요.

      libc.a
      libm.a
      libdb.a
      libelf.a
      libfl.a
      libg++.a
      libg.a
      libncurses.a
      libreadline.a
      libvga.a
      등등...

       이러한 라이브러리는 우리가 컴파일 과정을 거쳐서 만든 .o 화일을
      한 곳에 통들어 관리하는 것에 불과합니다. 따라서 archive 를 의미
      하는 .a 라고 이름을 짓게 된 것이죠. 라이브러리는 ``도서관''으로
      서 그냥 .o 를 무작위로 집어넣은 것은 아니고 당연히 도서관에는
      소장하고 있는 책에 대한 목록(index)을 가지듯 포함되어 있는 .o
      에 대한 인덱스(index)를 가지고 있습니다.

       라이브러리는 다음과 같이 구성되어 있다고 할 수 있는 것입니다.

            라이브러리 = 목차(index) + ( a.o + b.o + c.o + ... )
        
       libc.a 를 가지고 한 번 놀아볼까요? 라이브러리 아카이브를 관리하
      는 ar 이라는 GNU 유틸리티를 써보겠습니다.

      $ cd /usr/lib
      $ ar t libc.a
      assert-perr.o
      assert.o
      setenv.o
      ftime.o
      psignal.o
      mkstemp.o
      sigint.o
      realpath.o
      cvt.o
      gcvt.o
      ctype-extn.o
      ctype.o
      <등등... 계속>

      $ ar t libc.a | grep printf
      iofprintf.o
      ioprintf.o
      iosprintf.o
      iovsprintf.o
      iovfprintf.o
      printf_fp.o
      vprintf.o
      snprintf.o
      vsnprintf.o
      asprintf.o
      vasprintf.o
      printf-prs.o
      reg-printf.o
      $

       위에서 볼 수 있다시피 .o 화일들이 그 안에 들어있습니다.

       <주목>
       유닉스에서 라이브러리 이름은 lib 로 시작합니다.

간단하게 라이브러리를 하나 만들어서 사용해보도록 합시다.

이번 예제는 3 개의 화일로 이루어졌습니다.

        myfunc.h
        myfunc.c
        hello.c

첫번째 myfunc.h 헤더 화일의 내용입니다.


extern void say_hello ( void );

두번째 myfunc.c, 실제 함수 정의부입니다.


#include <stdio.h>
#include "myfunc.h"

void 
say_hello ( void )
{
  printf ( "Hello, Linux guys!\n" );
}

마지막으로 메인 함수(main)가 들어있는 hello.c 입니다.


#include "myfunc.h"

int
main ( void )
{
  say_hello ();
  return 0;
}

main 함수에서 say_hello 라는 함수를 사용하게 됩니다. 이 정도야 그냥 이렇게 해버리고 말죠 ^^

 $ gcc -o say_linux hello.c myfunc.c

하지만 라이브러리를 만들어보고 시험해보려고 하는 것이므로 일부러 어렵게 한 번 해보기로 하겠습니다.

 $ gcc -c myfunc.c
 $ ar r libmylib.a myfunc.o
 $ ar s libmylib.a
 $ ar t libmylib.a
 myfunc.o
 $ gcc -o say_linux hello.c -lmylib
                            ^^^^^^^^
 ld: cannot open -lmylib: No such file or directory

흠... 처음부터 만만치 않죠? ^^ 실패하긴 했지만 몇 가지를 일단 알아봅시다.

-l 옵션

링크(link)할 라이브러리를 명시해주는 옵션이 바로 -l ( 소문자 L ) 옵션입니다.

-I 옵션과 마찬가지로 바짝 붙여서 씁니다. 절대 떼면 안됩니다.

우리는 libmylib.a 라는 라이브러리를 만들어두었습니다. 그것을 사용하기 위해서는 -lmylib 라고 적어줍니다. 라이브러리 화일명에서 어떤 글자들을 떼내고 쓰는지 주목하십시요.

 libmylib.a
    ^^^^^  

앞의 lib 를 떼내고 맨 뒤에 붙는 .a 를 떼냅니다.

링크(link)라는 것이 어떤 것이 모르신다면 당장 C 프로그래밍 책을 다시 읽어보시기 바랍니다. 이 글에서 설명할 범위는 아닌 듯 합니다.

-L 옵션

ld 는 유닉스에서 사용되는 링커(Linker)입니다. C 프로그램 컴파일의 맨 마지막 단계를 맡게 되지요.

위에서 우리는 다음과 같은 에러 메세지를 만났습니다.

 ld: cannot open -lmylib: No such file or directory

자, 이제 배워야 할 옵션은 ``라이브러리의 위치를 정해주는'' -L ( 대문자 L ) 옵션입니다. 사용형식은 -L<디렉토리명> 입니다.

리눅스에서 어떤 라이브러리를 찾을 때는 /lib, /usr/lib, /usr/local/lib와 같은 정해진 장소에서만 찾게 되어 있습니다. 그것은 규칙이지요.

중요한 사실은 아무리 여러분 라이브러리를 현재 작업 디렉토리에 놓아두어도 ld 는 그것을 찾지 않는다는 사실입니다. ld 더러 라이브러리가 있는 장소를 알려주려면 다음과 같이 -L 옵션을 붙이십시요.

 $ gcc -o say_linux hello.c -lmylib -L.
                                    ^^^

-L. 은 현재 디렉토리에서 라이브러리를 찾으라는 말입니다. -L 옵션은 여러번 줄 수 있습니다.

성공적으로 컴파일되었을 겁니다.

 $ ./say_linux
 Hello, Linux guys!

지금까지 여러분은 gcc 옵션 중 두번째로 중요한 -I, -l, -L 옵션에 대하여 배우셨습니다. 그리고 또한 라이브러리 만들기에 대하여 맛보기를 하였습니다.


3. make 강좌

3.1 머릿말

소스 한두 개로 이루어진 C/C++ 언어 교양과목 과제물을 제출하는 것이 아니라면 약간만 프로젝트가 커져도 소스는 감당할 수 없을 정도로 불어나게 되고 그것을 일일이 gcc 명령행 방식으로 처리한다는 것은 상당히 곤역스러운 일입니다.

그래서 하나의 프로젝트를 효율적으로 관리하고 일관성있게 관리하기 위하여 Makefile 이라는 형식을 사용하고 make 라는 유틸리티를 사용합니다.

여러분이 리눅스에서 소스 형태로 되어 있는 것을 가져와서 컴파일하게 되면 보통 마지막에는 make 라는 명령, 또는 make <어쩌구> 이런 식으로 치게 됩니다.

make 라는 유틸리티는 보통 현재 디렉토리에 Makefile 또는 makefile 이라는 일정한 규칙을 준수하여 만든 화일의 내용을 읽어서 목표 화일(target)을 만들어냅니다. Makefile의 이름을 다르게 명시하고 싶을 때는 다음과 같이 합니다.

        $ make -f Makefile.linux

보통 멀티플랫폼용 소스들은 Makefile.solaris, Makefile.freebsd, Makefile.hp 이런 식으로 Makefile 을 여러 개 만들어두는 경향이 있지요. 또는 적절하게 만들어두어 다음과 같이 make <플랫폼> 라는 식으로 하면 컴파일되도록 하기도 합니다.

        $ make linux

이런 일은 보통의 관례일 뿐이죠. 더 예를 들어보자면 이런 식입니다. 우리가 커널 컴파일 작업할 때를 보십시요.

        $ make config           /* 설정 작업을 한다 */
        $ make dep              /* 화일 의존성을 검사한다 */
        $ make clean            /* 만든 화일들을 지우고 
                                   깨긋한 상태로 만든다 */
        $ make zImage           /* zImage(압축커널)를 만든다 */
        $ make zlilo            /* 커널을 만들고 LILO를 설정한다 */
        $ make bzImage          /* bzImage(비대압축커널)를 만든다 */
        $ make modules          /* 커널 모듈을 만든다 */
        $ make modules_install  /* 커널 모듈을 인스톨한다 */

복잡한 것같아도 우리는 항상 make, make, make ... 일관성있게 make 라고만 쳐주면 됩니다. ^^ 분량이 작은 소스들의 경우에는 일반적으로 다음만 해도 되는 경우가 많죠.

        $ make  또는 make all
        $ make install

영어권에 사는 사람들에게는 더욱 친밀하게 느껴질 겁니다. 그렇겠죠? ``만들라!''라는 동사를 사용하고 있는 것이고 그 다음에는 그들의 정상적인 어순에 따라 목적어가 나오죠.

        $ make install.man

또한 관례상 ``맨페이지'' 같은 것은 별도로 인스톨하도록 배려하는 경우가 많습니다. 프로그램에 대해 잘 아는 사람이라면 맨페이지를 자질구레하게 설치하고 싶지 않을 때도 많으니까요.

다른 사람에게 공개하는 소스라면 더욱 make 를 사용해야 합니다. 그들뿐 아니라 여러분 자신도 make 라고만 치면 원하는 결과가 나올 수 있도록 하는 것이 좋습니다. 많은 소스를 작성하다 보면 여러분 스스로도 까먹기 쉽상입니다.

일단 make를 사용하는 일반적인 관례를 익히는 것이 중요하다고 봅니다. 리눅스 배포판 패키지만 설치하지 마시고 적극적으로 소스를 가져다 컴파일해보십시요. 실력이든 꽁수든 늘기 시작하면 여러분은 더욱 행복해지실 수 있습니다. =)

3.2 make 시작해 봅시다.

일관성있게 make라고만 치면 모든 일이 술술 풀려나가도록 하는 마술은 Makefile이라는 것을 어떻게 여러분이 잘 만들어두는가에 따라 결정됩니다. 바로 이 Makefile 을 어떻게 만드는지에 대하여 오늘 알아봅니다.

상황 1)

        $ gcc -o foo foo.c bar.c

여기서 foo 라는 실행화일은 foo.c, bar.c 라는 2 개의 소스로부터 만들어지고 있습니다.

여러분이 지금 계속 코딩을 하고 있는 중이라면 이 정도쯤이야 가상콘솔 또는 X 터미널을 여러 개 열어두고 편집하면서 쉘의 히스토리 기능을 사용하면 그만이지만 하루 이틀 계속 해간다고 하면 곤역스러운 일이 아닐 수 없습니다.

자, 실전으로 들어가버리겠습니다. vi Makefile 해서 만들어봅시다. ( 편집기는 여러분 마음 )


 foo:   foo.o bar.o 
        gcc -o foo foo.o bar.o

 foo.o: foo.c
        gcc -c foo.c

 bar.o: bar.c
        gcc -c bar.c

입력하는데 주의하실 것이 있습니다. 자, 위 화일을 보십시요. 형식은 다음과 같습니다.


 목표:  목표를 만드는데 필요한 구성요소들...
        목표를 달성하기 위한 명령 1
        목표를 달성하기 위한 명령 2
        ...

Makefile은 조금만 실수해도 일을 망치게 됩니다.

맨 첫번째 목표인 foo 를 살펴보죠. 맨 첫 칸에 foo: 라고 입력하고 나서 foo가 만들어지기 위해서 필요한 구성요소를 적어줍니다. foo가 만들어지기 위해서는 컴파일된 foo.o, bar.o 가 필요합니다. 각 요소를 구분하는데 있어 콤마(,) 같은 건 사용하지 않고 공백으로 합니다.

중요! 중요! 그 다음 줄로 넘어가서는 <탭>키를 누릅니다. 꼭 한 번 이상은 눌러야 합니다. 절대 스페이스키나 다른 키는 사용해선 안됩니다. 목표 화일을 만들어내기 위한 명령에 해당하는 줄들은 모두 <탭>키로 시작해야 합니다. Makefile 만들기에서 제일 중요한 내용입니다. <탭>키를 사용해야 한다는 사실, 바로 이것이 중요한 사실입니다.

foo를 만들기 위한 명령은 바로 gcc -o foo foo.o bar.o 입니다.

다시 한 번 해석하면 이렇습니다. foo 를 만들기 위해서는 foo.o와 bar.o가 우선 필요하다.( foo: foo.o bar.o )

일단 foo.o, bar.o 가 만들어져 있다면 우리는 gcc -o foo foo.o bar.o 를 실행하여 foo 를 만든다.

자, 이제부터 사슬처럼 엮어나가는 일만 남았습니다.

foo를 만들려고 하니 foo.o와 bar.o 가 필요합니다!

그렇다면 foo.o는 어떻게 만들죠?


 
 foo.o: foo.c
        gcc -c foo.c

바로 이 부분입니다. foo.o는 foo.c를 필요로 하며 만드는 방법은 gcc -c foo.c입니다.

그 다음 bar.o 는 어떻게 만들죠?


 bar.o: bar.c
        gcc -c bar.c

이것을 만들려면 이것이 필요하고 그것을 만들기 위해서는 또 이것이 필요하고...

소스를 만들어서 해봅시다.

  • foo.c 의 내용

extern void bar ( void );

int
main ( void )
{
  bar ();
  return 0;
}

  • bar.c 의 내용

#include <stdio.h>

void
bar ( void )
{
  printf ( "Good bye, my love.\n" );
}

Makefile을 위처럼 만들어두고 그냥 해보죠.

        $ make 또는 make foo
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

명령이 실행되는 순서를 잘 보십시요. 여기서 감이 와야 합니다. ^^

        $ ./foo
        Good bye, my love.

다시 한 번 실행해볼까요?

        $ make
        make: `foo' is up to date.

똑똑한 make는 foo를 다시 만들 필요가 없다고 생각하고 더 이상 처리하지 않습니다.

이번에는 foo.c 를 약간만 고쳐봅시다. return 0; 라는 문장을 exit (0); 라는문장으로 바꾸어보죠. 그리고 다시 한 번 다음과 같이 합니다.

        $ make
        gcc -c foo.c
        gcc -o foo foo.o bar.o

자, 우리가 원하던 결과입니다. 당연히 foo.c 만 변화되었으므로 foo.o 를 만들고 foo.o가 갱신되었으므로 foo도 다시 만듭니다. 하지만 bar.c는 아무변화를 겪지 않았으므로 이미 만들어둔 bar.o 는 그대로 둡니다.

소스크기가 늘면 늘수록 이처럼 똑똑한 처리가 필요하지요.

        $ rm -f foo
        $ make
        gcc -o foo foo.o bar.o

이것도 우리가 원하던 결과입니다. foo 실행화일만 살짝 지웠더니 make는 알아서 이미 있는 foo.o, bar.o 를 가지고 foo 를 만들어냅니다. :)

상황 2) 재미를 들였다면 이번에는 청소작업을 해보기로 합시다.


 clean:
        rm -f foo foo.o bar.o

이 두 줄을 위에서 만든 Makefile 뒷부분에 추가해보도록 합시다.

        $ make clean
        rm -f foo foo.o bar.o
        $ make
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

make clean이라는 작업 또한 중요한 작업입니다. 확실히 청소를 보장해주어야 하거든요.

make, make clean 이런 것이 되면 상당히 멋진 Makefile 이라고 볼 수 있죠? 이번 clean 에서 보여드리고자 하는 부분은 이런 것입니다.

우리의 머리 속에 clean 이라는 목표는 단지 화일들을 지우는 일입니다.

clean: 옆에 아무런 연관 화일들이 없지요?

그리고 오로지 rm -f foo foo.o bar.o 라는 명령만 있을 뿐입니다. clean이라는 목표를 수행하기 위해 필요한 것은 없습니다. 그러므로 적지 않았으며 타당한 make 문법입니다.

상황 3)


 all: foo

이 한 줄을 Makefile 맨 앞에 넣어두도록 합시다.

        $ make clean
        $ make all
        gcc -c foo.c
        gcc -c bar.c
        gcc -o foo foo.o bar.o

이번예는 all 이라는 목표에 그 밑에 나오는 다른 목표만이 들어있을 뿐, 아무런 명령도 없는 경우입니다. 보통 우리는 make all 하면 관련된 모든 것들이 만들어지길 원합니다.

 all: foo1 foo2 foo3
 foo1: <생략>
 foo2: <생략>
 foo3: <생략>

이런 식으로 해두면 어떤 장점이 있는지 알아봅시다.

보통 make all 하면 foo1, foo2, foo3가 모두 만들어집니다. 그런데 어떤 경우에는 foo1만 또는 foo2만을 만들고 싶을 때도 있을 겁니다. 괜히 필요없는 foo3 같은 것을 컴파일하느라 시간을 보내기 싫으므로 우리는 단지 다음과 같이만 할 겁니다.

        $ make foo1
        $ make foo2

물론 일반적으로 다 만들고 싶을 때는 make all 이라고만 하면 됩니다.

make all 이건 아주 일반적인 관례이지요. 그리고 외우기도 쉽잖아요?

3.3 꼬리말 규칙, 패턴 규칙

잘 관찰해보시면 어쩌구.c -----------> 어쩌구.o 라는 관계가 매번 등장함을 알 수 있습니다. 이것을 매번 반복한다는 것은 소스 화일이 한 두 개 정도일 때야 모르지만 수십 개가 넘게 되면 정말 곤역스러운 일이라고 하지 않을 수 없지요.

다음과 같은 표현을 Makefile 에서 보는 경우가 많을 겁니다.


 .c.o:
        gcc -c ${CFLAGS} $<

여기서 .c.o 의 의미를 생각해보겠습니다. ".c 를 입력화일로 받고 .o 화일을 만든다"

        gcc -c ${CFLAGS} $<

이 문자을 보면 일단 눈에 띄는 것은 ${CFLAGS}라는 표현과 $< 라는 암호와도 같은 표현입니다. 여기서는 일단 $< 라는 기호의 의미를 알아보겠습니다.

유닉스에서 쉘을 잘 구사하시는 분들은 눈치채셨을 겁니다. 작다 표시(<)는 리다이렉션에서 입력을 의미하는 것을 아십니까? 그렇다면 $< 는 바로 .c.o 라는 표현에서 .c 즉 C 소스 화일을 의미합니다.

예를 들어 foo.c 가 있다면 자동으로

        gcc -c ${CFLAGS} foo.c

가 수행되며 gcc 에 -c 옵션이 붙었으므로 foo.o 화일이 만들어질 것입니다.

3.4 GNU make 확장 기능

.c.o 라는 전통적인 표현 말고 GNU 버전( 우리가 리눅스에서 사용하는 것은 바로 이것입니다 )의 make 에서 사용하는 방법을 알아봅시다.

위에서 예로 든 것을 GNU 버전의 make 에서 지원하는 확장문법을 사용하면 다음과 같습니다.


 %.o: %.c
        gcc -c -o $@ ${CFLAGS} $<

그냥 설명 전에 잘 살펴보시기 바랍니다.

우리가 위에서 알아보았던 표준적인 .c.o 라는 꼬리말 규칙(Suffix rule)보다 훨씬 논리적이라는 것을 발견하셨습니까?

우리가 바로 전 강의에서 main.o : main.c 이런 식으로 표현한 것과 같은 맥락이지요? 이것을 우리는 패턴 규칙(Pattern rule)이라고 부릅니다. 콜론(:) 오른쪽이 입력 화일이고 왼쪽이 목표 화일입니다. 화일명 대신 퍼센트(%) 문자를 사용한 것만 유의하면 됩니다. 여기서 foo.c 라는 입력화일이 있다면 % 기호는 foo 만을 나타냅니다.

        gcc -c -o $@ ${CFLAGS} $<

라는 표현을 해석해봅시다. ( 후  마치 고대 문자판을 해석하는 기분이 안드십니까? ^^ )

$< 는 입력화일을 의미하고 $@ 은 출력화일을 의미합니다. .c.o와 같은 꼬리말 규칙과 별 다를 바 없다고 생각하실 지 모르나 -o $@ 를 통하여 .o 라는 이름 말고 전혀 다른 일도 해낼 수 있습니다.

다음 예는 그냥 이런 예가 있다는 것만 한 번 보아두시기 바랍니다.


 %_dbg.o: %.c
        gcc -c -g -o $@ ${CFLAG} $<

 DEBUG_OBJECTS = main_dbg.o edit_dbg.o

 edimh_dbg: $(DEBUG_OBJECTS)
        gcc -o $@ $(DEBUG_OBJECTS)

%_dbg.o 라는 표현을 잘 보십시요. foobar.c 라는 입력화일(%.c)이 있다면 % 기호는 foobar 를 가리키므로 %_dbg.o 는 결국 foobar_dbg.o 가 됩니다.

기호정리

 $<     입력 화일을 의미합니다. 콜론의 오른쪽에 오는 패턴을 치환합니다.
 $@     출력 화일을 의미합니다. 콜론의 왼쪽에 오는 패턴을 치환합니다.
 $*     입력 화일에서 꼬리말(.c, .s 등)을 떼넨 화일명을 나타냅니다.

역시 GNU 버전이라는 생각이 들지 않으시는지요?

3.5 매크로(Macro) 기능

앞에서도 잠깐씩 나온 ${CFLAGS} 라는 표현을 보도록 합시다.

gcc 옵션도 많이 알고 make을 능수능란하게 다룰 수 있는 사람들은 다음과 같이 해서 자신의 프로그램에 딱 맞는 gcc 옵션이 무엇인지 알아내려고 할 것입니다.

 $ make CFLAGS="-O4"
 $ make CFLAGS="-g"

이제 매크로에 대한 이야기를 나눠볼까 합니다. 이 이야기를 조금 해야만 위의 예를 이해할 수 있다고 보기 때문입니다. 그냥 시험삼아 해보십시다. 새로운 것을 배우기 위해서는 꼭 어떤 댓가가 와야만 한다는 생각을 버려야겠지요?


 myprog: main.o foo.o
        gcc -o $@ main.o foo.o

이것을 괜히 어렵게 매크로를 이용하여 표현해보기로 하겠습니다.


 OBJECTS = main.o foo.o
 myprog: $(OBJECTS)
        gcc -o $@ $(OBJECTS)

여러분은 보통 긴 Makefile을 훔쳐 볼 때 이런 매크로가 엄청나게 많다는 것을 보신 적이 있을 겁니다. ^^


 ROOT = /usr/local
 HEADERS = $(ROOT)/include
 SOURCES = $(ROOT)/src

예상하시듯 위에서 HEADERS는 당연히 /usr/local/include가 되겠지요?

다음과 같은 문장도 있습니다.


 ifdef XPM
     LINK_DEF = -DXPM
 endif

  $ make XPM=yes

이렇게 하면 ifdef   endif 부분이 처리됩니다.

자, make CFLAGS="-O" 이런 명령을 한 번 봅시다. ${CFLAGS}에서 {} 표현은 유닉스 쉘에서 변수값을 알아낼 때 쓰는 표현입니다. CFLAGS 값을 여러분이 Makefile에 고정적으로 집어넣지 않고 그냥 make 만 실행하는 사람에게 선택권을 주기 위해서 사용하거나 자기 스스로 어떤 옵션이 제일 잘 맞는지 알아보기 위해서 사용합니다. 다른 옵션으로 컴파일하는 것마다 일일이 다른 Makefile을 만들지 말고 가변적인 부분을 변수화하는 것이 좋습니다.

3.6 마지막 주의 사항


 target:
        cd obj
        HOST_DIR=/home/e 
        mv *.o $HOST_DIR

하나의 목표에 대하여 여러 명령을 쓰면 예기치 않은 일이 벌어집니다. 기술적으로 말하자면 각 명령은 각자의 서브쉘에서 실행되므로 전혀 연관이 없습니다. -.- cd obj 도 하나의 쉘에서 HOST_DIR=/home/e도 하나의 쉘에서 나머지도 마찬가지입니다. 각기 다른 쉘에서 작업한 것처럼 되므로 cd obj 했다 하더라도 다음번 명령의 위치는 obj 디렉토리가 아니라 그대로 변함이 없이 현재 디렉토리입니다. 세번째 명령에서 HOST_DIR 변수를 찾으려 하지만 두번째 명령이 종료한 후 HOST_DIR 변수는 사라집니다.


 target:
        cd obj ; \
        HOST_DIR=/hom/e ; \
        mv *.o $$HOST_DIR

이렇게 적어주셔야 합니다. 세미콜론으로 각 명령을 구분하지요. 처음 두 줄의 마지막에 쓰인 역슬래쉬(\) 문자는 한 줄에 쓸 것을 여러 줄로 나누어 쓴다는 것을 나타내고 있습니다.

주의! 세번째 줄에 $HOST_DIR이 아니라 $$HOST_DIR인 것을 명심하십시요. 예를 하나 들어보죠. ^^


 all:
         HELLO="안녕하세요?";\
         echo $HELLO

Makefile의 내용을 이렇게 간단하게 만듭니다.

 $ make
 HELLO="안녕하세요?";\
 echo ELLO
 ELLO
<verb>

 우리가 원하는 결과가 아니죠?

 $HELLO를 $$HELLO로 바꾸어보십시요.

<verb>
 $ make
 HELLO="안녕하세요?";\
 echo $HELLO
 안녕하세요?


 all:
         @HELLO="안녕하세요?"; echo $$HELLO

명령의 맨 처음에 @ 문자를 붙여봅시다.

 $ make
 안녕하세요?

3.7 잠시 마치면서

Makefile에 대한 내용은 이것보다 훨씬 내용이 많습니다. 하지만 모든 것을 다 알고 시작할 수는 없겠지요? 이 정도면 어느 정도 충분하게 창피하지 않을 정도의 Makefile을 만들 수 있습니다.

참고로 autoconf/automake라고 하는 아주 훌륭한 GNU make 유틸리티를 시간나면 배워보시는 것도 좋습니다.

시간을 내서 리눅스에서의 C 프로그래밍에 필요한 다른 여러 가지 유틸리티들( 간접적이든 직접적이든 grep, awk, rcs, cvs 등 )의 간단/실전 사용법도 올려드릴까 생각 중입니다. ^^



출처: 이 문서는 나우누리 "Linux 사용자 모임" 12-2번 강좌란 게시판에 올라온 이만용님의 강좌 "gcc와 make에 대한 강좌"를 sgml문서로 만든 것입니다.


참고문헌

Running LINUX(Matt Welsh, Lar Kaufman), "오렐리 출판사"

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기
Posted by 소리나는연탄.
TAGS ,

Leave your greetings here.

  
  
  
  
  
  
  
  
 
« Previous : 1 : ... 5 : 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : ... 33 : Next »