쓰레드를 사용한 포트 스캔 프로그램
출처 : http://beist.org/research/public/pthread-scan/index.html



by Beist Security Study Group
(http://beist.org)

Members of Beist Research Group : beist and anonymous people
Members of Beist Study Group : beist, dars21, obhacker, passion, p-jackpot, jacaranda, cina





요약: 쓰레드를 이용하여 프로그램을 병렬적으로 작성할 경우 원하는 기능을 보다 빠르게 수행할 수 있습니다. 본 문서는 쓰레드를 이용한 포트 스캔 프로그램을 작성하는 과정을 담았습니다. 이를 활용할 경우 쓰레드를 사용하지 않은 포트 스캔보다 훨씬 더 빠르게 스캔 작업을 수행할 수 있고 특히 광범위한 IP 대역을 스캔하거나 할 때 유용하게 사용될 수 있습니다.










1. 소개

 

쓰레드 생성은 프로세스 생성에 비해 시스템 자원을 많이 소비하지 않고 가볍게 동작하기 때문에 네트워크 프로그래밍에서 다중 접속을 처리 할 때나 프로세서 내의 동일 작업을 병렬적으로 처 리할 때 많이 사용되는 기법입니다.

Port Scan 작업은 목표 네트워크를 공략하기 위한 중요한 작업 중에 하나인데 네트워크 환경에 따라 Scan 작업이 굉장히 오래 걸리는 경우가 있을 수 있습니다 . 특히 큰 대역을 Scan할 때 등 아주 많은 Port를 검색할 때 문제가 될 수 있습니다. 본 문서는 이러한 문제점을 극복하기 위한 방안으로 쓰레드를 Port Scan에 도입하는 내용에 대해서 다룰 것입니다. 또한 본 문서는 Port Scan쓰레드 기능을 도입하여 Scan 시간을 낭비하지 않도록 하는 것이 목표이므로 많은 내용을 다루지 않고 Scan 작업에 직접적으로 관련된 쓰레드만을 설명할 것입니다.

 

 

 

 

2. 기술적인 내용

 

(1)  쓰레드란

 

쓰레드는 흔히 경량 프로세스, 세미 프로세스라 지칭하는데, 프로그램에서 수행하는 명령의 흐름을 동시에 처리하도록 하는 것이 가장 큰 특징입니다. 일반적으로 프로세스의 경우 프로그램 명령들이 단일적으로 혹은 순차적으로 처리되지만, 쓰레드를 사용하게 되면 프로그램 명령들을 병렬적으로 처리할 수 있기 때문에 매우 빠른 속도로 동작할 수 있게 됩니다. 예를 들어 수행해야 할 작업이 많은 프로그 램일 경우 쓰레드 사용 여부에 따라 처리 속도가 대략 스무 배 이상 차이가 날 수 있습니다. 이외에도 쓰레드 간에는 공유 메모리 공간을 갖고 있기 때문에 프로그램 구현이 편리하다는 장점이 있습니다.

쓰레드의 단점으로, 작업을 수행하는 도중, 동시에 특정 메모리에 접근했을 때 문제를 일으킬 수도 있습니다. 또 동작중인 여러 개의 쓰레드 중 하나라도 잘못된 연산을 하게 될 경우 운영체제에 의해 프로세스 전체가 죽게 됩니다. 그리고, 여러 명령이 동시에 실행되기 때문에 디버깅 작업이 어렵다는 단점이 있습니다.

 

(2)  쓰레드 API

 

본 문서에서 다루게 될 쓰레드 API POSIX 쓰레드인데 운영체제에 관계없이 사용될 수 있는 호환성이 좋은 라이브러리로 pthread(Posix Thread줄임말)입니다.

 

1)    쓰레스 생성  

 

#include<pthread.h>

 

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

 

thread : 쓰레드 식별자. 다른 쓰레드와 구분될 수 있는 ID할당받게 됩니다.

attr : 생성되는 쓰레드의 특성을 정하기 위해 사용되는데, 일반적으로 NULL을 사용합니다.

start_routine : 리턴 타입이 void*이고 인자도 void*인 함수 포인터로 쓰레드가 실행시킬쓰레드 함수입니다.

arg : 쓰레드에 의해 호출되는 함수(start_routine 포인터에 대입된 함수)전될되는 함수 인자 입니다.

 

이 함수에서 중요한 인자는 쓰레드 식별자(thread), 쓰레드 생성시 호출되는 함수(start_routine)입니다.

 

 

2) 쓰레드 기다림

 

#include<pthread.h>

 

int pthread_join(pthread_t th, void **thread_return);

 

th : 쓰레드가 종료할 때까지 기다리는 쓰레드 식별자입니다.

thread_return : 쓰레드가 종료시 반환하는 값에 접근할 수 있는 포인터입니다.

 

생성된 쓰레드가 종료될 때까지 기다리기 위한 함수입니다. 만약 프로세스가 쓰레드를 생성한 후에 쓰레드가 종료할 때까지 기다리지 않고 프로세스를 종료하면 쓰레드도 동시에 모두 종료되기 때문에 모든 쓰레드를 안전하게 실행시키기 위해서 반드시 사용되어야 하는 함수 입니다.

 

3) 쓰레드 식별자 반환

 

#include<pthread.h>

 

int pthread_self();

 

pthread_self 함수를 실행하면 해당 쓰레드의 식별자를 반환해줍니다.

 

 

 

4) 쓰레드 분리

 

#include<pthread.h>

 

int pthread_detach(pthread_t th);

 

th : 메인 쓰레드에서 분리할 쓰레드 식별자이고, 쓰레드가 분리되면 종료되면서 자원을 즉시 해제시켜 줍니다.

 

5) 쓰레드 종료

 

#include<pthread.h>

 

int pthread_exit(void *retval);

 

현재 실행 중인 쓰레드를 종료시킬 때 사용되는 함수입니다. 종료 후에 pthread_join함수를 통하여 모든 자원을 해제 시킵니다.

 

 

(3) 쓰레드 동기화 API

 

쓰레드는 하나의 프로세스에서 나뉘어진 것으로 데이터를 공유할 수 있다고 하였습니다. 여러 개의 쓰레드가 전역 변수에 접근하는 경우에 문제가 발생할 수 있는데 이것을 임계 영역(Critical Section)으로 볼 수 있습니다. 이 문서에서는 뮤텍스(mutex)를 사용하여 데이터 동시 접근 문제를 해결합니다. 뮤텍스를 지원하는 pthread의 함수를 알아보겠습니다.

 

#include<pthread.h>

 

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

 

int pthread_mutex_lock(pthread_mutex_t *mutex);

 

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

 

 pthread_mutex_init : 뮤텍스를 사용하기 전에 초기화 과정을 거치기 위한 함수입니다. 첫 번째 인자에는 초기화하고자 하는 뮤텍스 포인터를 넘기고, 두 번째 인자에는 뮤텍스 속성(attribute)를 설정할 때 사용합니다. 속성을 사용하지 않을 때는 NULL포인터를 넘깁니다.

pthread_mutex_lock : 뮤텍스를 걸어 잠글 때(lock) 사용하는 함수입니다. 뮤텍스를 걸어 잠그면 후에 진입하는 쓰레드는 뮤텍스가 풀릴 때까지 대기하게 됩니다.

pthread_mutex_unlock : 뮤텍스를 풀어줄 때 사용하는 함수입니다. 뮤텍스를 잠그면 반드시 풀어줘야 다른 쓰레드들이 진입할 수 있습니다.

pthread_mutex_destroy : 더 이상 뮤텍스를 사용하지 않고 이와 관련된 리소스를 해제할 때 사용되는 함수입니다.

 

(4) 멀티 쓰레드 구현 예 (포트스캔 프로그램)

 

멀티 쓰레드 프로그래밍 모델은 네트워크 프로그래밍에서 채팅 서버 등, 다중 접속 처리를 위한 목적으로 많이 사용됩니다. 본 문서에서는 이것을 응용하여 목표 서버의 포트(65535) 중에 어떤 포트가 열려있는지 빠른 시간 안에 확인할 수 있는 포트스캔 프로그램을 제작하는 과정으로 예를 들어 설명하겠습니다 .

예제 프로그램인 포트스캔 프로그램에서 65535개의 포트를 병렬적으로 검색하기 위해 멀티 쓰레드를 사용합니다. 시스템 자원의 한계 문제점으로 인해 일반적으로 프로세스당 쓰레드 생성개수가 제한되어 있습니다. 또한 과도한 쓰레드를 생성할 경우 프로그램이 정상적으로 작동되지 않는 경우가 있는데 본 문서에서는 이 문제점을 해결하기 위해 쓰레드를 생성한 후, 쓰레드 생성개수(thread_cont)를 증가시키는 부분을 임계영역으로 지정하고 뮤텍스 잠금을 통해 제한된 수(1000)를 넘지 않도록 하고 있습니다. (1000개 수치는 시스템마다 유연하게 설정할 수 있습니다.)

메인 함수에 대한 내용은 생략하고 쓰레드를 생성하는 함수와 쓰레드에서 실행할 내용에 대해서만 설명하겠습니다. 메인 함수의 역할은 프로그램을 실행시킬 때 입력하는 명령 인수로부터(argument) 목표 시스템의 IP주소를 얻어와 thread_scan(쓰레드를 생성할 함수) 함수를 호출하는 것 입니다.

 

1

2 #define ENDPORT 65535

3 int thread_count; //쓰레드 생성 개수를 나타내는 변수

4 int dst_ip; //스캔대상 ip

5 int flag = 1;

6

7 void thread_scan()  //쓰레드를 생성할 함수

8 {

9            int i, start_port;

10          pthread_t thread_id[ENDPORT];   

11          void* t_return;

12          int value[ENDPORT];

13          struct sockaddr_in sock;

14      if(pthread_mutex_init(&mutex, NULL)) fprintf(stderr, "mutex init error\n");

15          printf("\nStart PortScan..........\n");

16          printf("port\tstate\n");

17          for(i=0, start_port = 0;i<ENDPORT;i++){      

18                        value[i]=++start_port;

19      pthread_mutex_lock(&mutex);

20          while(1){

21      if(thread_count < 1000){

22       if(pthread_create(&thread_id[i], NULL, tcpportscan, (void*)&value[i])<0)

23                                     fprintf(stderr, "create error\n");

24                                thread_count++;

25                                     break;

26          }

27          else{

28                                     usleep(10);

29                                     continue;

30          } // if(thread_count<1000)

31          } // while()

32          pthread_mutex_unlock(&mutex);

33          } //for

34          if(flag)

35                                     printf("All port close..........\n");

36 }

37

38

39  void *tcpportscan(void* arg) //쓰레드에서 실행할 함수

40  {

41          struct sockaddr_in dest; //소켓 어드레스

42          int sockfd; //소켓 디스크립트

43          int scanport;

44          int value;

45          int Error, ErrorLength;

46          struct timeval tv;

47          fd_set readfd, writefd;

48          pthread_t th;

49          th = pthread_self();

50          scanport= *(int*)arg;

51          memset((char*)&dest, 0, sizeof(dest));

52          dest.sin_family = AF_INET;

53          dest.sin_port = htons(scanport);

54          dest.sin_addr.s_addr = htonl(dst_ip);

55

56          if((sockfd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0){

57                        fprintf(stderr, "소켓에러\n");

58                        close(sockfd);

59                        pthread_detach(th);

60          }

61          value = fcntl(sockfd, F_GETFL);

62          fcntl(sockfd, F_SETFL, value | O_NONBLOCK);

63          if(connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) == 0){

64                        printf("connect에러");                     

65                        close(sockfd);

66                        pthread_detach(th);

67          }

68          //select 타임아웃 설정

69          tv.tv_sec = 2; //대기시간 2초 설정

70          tv.tv_usec = 0;

71      //select에서 검사하는 디스크립트 설정

72          FD_ZERO(&readfd);

73          FD_ZERO(&writefd);

74          FD_SET(sockfd, &readfd);

75          FD_SET(sockfd, &writefd);

76

77          if(select(sockfd+1, &readfd, &writefd, NULL, &tv) <= 0) ;

78     else{

79        if(FD_ISSET(sockfd, &readfd) || FD_ISSET(sockfd, &writefd)){

80                        ErrorLength = sizeof(Error);

81     if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &Error, &ErrorLength) == 0) {

82                                     if(Error == 0){

83                 & nbsp;             flag = 0; //열린 포트 있다는 flag 설정

84                                      &nb sp;            printf("%d\topen\n", scanport);             

85                                     }                                     

86      }//getsock

87              }//fd_isset

88     }//else

89          close(sockfd);

90      thread_count--;

91      pthread_detach(th);

92 }

 

 

 

프로그램 해설

 

2 : 최대 포트의 할당 개수가 65535이므로 검색포트의 개수를 65535개로 지정하고 있습니다.

4~5행 : 모든 쓰레드에서 참조 할 수 있도록 스캔 대상 호스트 IP전역번수로 선언하고, 검색된 포트가 없을 때 메시지를 출력해주기 위한 flag를 선언합니다.

14 : 동기화에 필요한 뮤텍스를 사용하기 위해 pthread_init함수를 호출하고 있다.

17~33행 : for문을 통해 65535까지 port번호를 대입하는 과정을 반복합니다.

한 프로세스의 쓰레드 생성 개수가 제한되어 있기 때문에 무제한으로 쓰레드를 생성되는 것

을 방지하기 위해 뮤텍스를 사용합니다. for문 시작 부분에서 뮤텍스 잠금함수인 pthread

_mutex_lock함수를 호출하여 임계영역으로 설정하고 쓰레드 생성개수가 1000개 이하인지

체크합니다. 만약 쓰레드 생성개수가 1000개 이하이면 pthread_create함수를 이용하여

 쓰레드를 생성하고 세 번째 인자에 실행될 쓰레드 함수로 tcpportscan함수를 대입하였습

니다. 또한, tcpportscan함수에 할당될 인자로는 value배열의 값으로 대입하였습니다.

쓰레드를 생성하고 난 후에는 pthread_mutex_unlock함수를 이용하여 뮤텍스 잠금을 풀어

줍니다.

반면에 쓰레드 생성개수가 1000개를 넘게 되면 다른 쓰레드에서 쓰레드가 종료될 때까지

기다리도록 합니다.

34~35행 : 열려있는 포트가 없을 경우 출력해주는 메시지입니다.

39 : 쓰레드 마지막 부분에서 pthread_detach함수의 인자로 사용하기 위해 쓰레드 식별자를 얻어오는 pthread_self함수를 호출합니다.

50 : 쓰레드 실행할 함수(tcpportscan)를 호출할 때 넘어온 인자 값으로 port번호입니다.

56 : socket함수를 통해 소켓 파일디스크립터를 생성합니다.

59, 66, 92행 : 쓰레드를 종료하고 자원을 해제하기 위해 pthread_detach함수를 호출합니다.

61~62행 : fcntl는 소켓파일 지정자 함수로 F_GETFL를 통해 파일 지정자 플래그를 읽어오고, F_SETFLO_NONBLOCK 플래그를 지정하여 소켓이 블록킹되지 않도록 합니다.

63 : connect 함수를 통해 포트에 연결 요청합니다.

69~70행 :  select함수가 기다리는 타임아웃 시간(2초 설정)을 지정합니다.

72~75행 :  FD_ 시작하는 함수는 파일 디스크립터를 제어하기 위한 함수로, FD_ZERO를 통해 입력과 출력의 파일 디스크립터 테이블을 0으로 초기화 하고, FD_SET를 통해 소켓을 입력과 출력의 파일 디스크립터 테이블에 설정합니다.

77 : select함수를 통해 tv구조체 변수에 설정된 시간동안 파일 디스크립터 테이블을 검사합니다.

78~88행 : FD_ISSET매크로를 통해 다시 한번 소켓의 입,출력의 변화인지 확인합니다. 그리고, getsockopt함수에 SO_ERROR 옵션을 지정해 소켓 에러 상태를 반환해주는데, 반환 값이 0이고, Error변수에 0이 할당되면 에러가 없는 값이므로 정상적인 소켓의 입, 출력 변화라고 판단하고 열려있는 포트번호를 출력해줍니다. flag변수는 모든 포트가 닫혀있을 때의 출력문을 위한 변수입니다.

 

 

 

 

3. 마치는 말

 

지금까지 멀티 쓰레드에 대한 개념과 멀티 쓰레드 프로그래밍을 응용하여 포트 스캔 프로그램을 작성하는 방법을 살펴보았습니다. 예제 프로그램처럼 멀티 쓰레드의 장점을 잘 활용하여 네트워크 프로그래밍을 한다면 프로세스만으로 실행하는 프로그램보다 훨 씬 더 좋은 성능을 발휘할 수 있는 프로그램을 기대할 수 있습니다. 특히 광범위한 IP 대역을 Scan할 때 쓰레드를 사용하지 않는다면 굉장히 오랜 시간이 걸릴 것입니다.

멀티 쓰레드를 사용할 때는 항상 공유 메모리를 사용하는 것에 주의해야 하고, 동기 화 문제를 잘 고려해서 프로그래밍을 해야 안전하게 작동될 수 있습니다. 본 문서에서는 쓰레드에 대한 내용을 아주 기본적인 부분만 다루었기 때문에 쓰레드를 보다 능숙하게 사용하기 위해서는 디버깅과 실시간 스케줄링, 고급 동기화, 공유 메모리에 대해 더욱 깊은 이해가 필요합니다.

 






소스 첨부: pthread_scan.c
#include<stdio.h> #include<netinet/ip.h> #include<fcntl.h> #include<pthread.h> #define ENDPORT 65535 int dst_ip; int thread_count; int flag = 1; pthread_mutex_t mutex; void *tcpportscan(void* arg); void thread_scan(); int main(int argc, char* argv[]) { int start_ip; if(argc != 2) { fprintf(stderr, "사용법:%s IP주소\n", argv[0]); fprintf(stderr, "예) %s 192.168.0.1\n", argv[0]); exit(1); } dst_ip = ntohl(inet_addr(argv[1])); thread_scan(); } void thread_scan() { int i, start_port; pthread_t thread_id[ENDPORT]; int value[ENDPORT]; struct sockaddr_in sock; if(pthread_mutex_init(&mutex, NULL)) fprintf(stderr, "mutex init error\n"); printf("\nStart PortScan..........\n"); printf("port\tstate\n"); for(i=0, start_port = 0;i<ENDPORT;i++) { value[i]=++start_port; pthread_mutex_lock(&mutex); while(1) { if(thread_count < 1000) { if(pthread_create(&thread_id[i], NULL, tcpportscan, (void*)&value[i])<0) fprintf(stderr, "create error\n"); thread_count++; break; } else { usleep(10); continue; } } pthread_mutex_unlock(&mutex); } if(flag) printf("All port close..........\n"); } void *tcpportscan(void* arg) { struct sockaddr_in dest; int sockfd; int scanport; int value; int Error, ErrorLength; struct timeval tv; fd_set readfd, writefd; pthread_t th; th = pthread_self(); scanport= *(int*)arg; memset((char*)&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(scanport); dest.sin_addr.s_addr = htonl(dst_ip); if((sockfd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0) { fprintf(stderr, "소켓에러\n"); close(sockfd); pthread_detach(th); } value = fcntl(sockfd, F_GETFL); fcntl(sockfd, F_SETFL, value | O_NONBLOCK); if(connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) == 0) { printf("connect에러\n"); close(sockfd); pthread_detach(th); } tv.tv_sec = 2; tv.tv_usec = 0; FD_ZERO(&readfd); FD_ZERO(&writefd); FD_SET(sockfd, &readfd); FD_SET(sockfd, &writefd); if(select(sockfd+1, &readfd, &writefd, NULL, &tv) <= 0) { } else { if(FD_ISSET(sockfd, &readfd) || FD_ISSET(sockfd, &writefd)) { ErrorLength = sizeof(Error); if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &Error, &ErrorLength) == 0) { if(Error == 0) { flag = 0; printf("%d\topen\n", scanport); } } } } close(sockfd); thread_count--; pthread_detach(th); }
신고
Posted by remos

정적라이브러리 ( 출처 : http://teamblog.joinc.co.kr/yundream/279 )

static library라고 부르기도 한다. 이 라이브러리는 단순한 오브젝트의 모음일 뿐이다. 정적라이브러리는 ar이라는 프로그램을 통해서 만들 수 있다. 그럼 ar을 이용해서 위의 사칙연산을 위한 4개의 오브젝트를 모아서 libmycalc.a라는 이름의 정적라이브러리를 생성해보도록 하자. rc 옵션을 이용하면, 정적라이브러리를 만들 수 있다.

r은 정적라이브러리를 만들겠다는 옵션이고, c는 새로 생성을 하겠다는 옵션이다.
# ar rc libmycalc.a sum.o sub.o mul.o div.o
libmycalc.a 라는 파일이 생성된걸 확인할 수 있을 것이다. t 옵션을 이용하면, 해당 라이브러리가 어떤 오브젝트를 포함하고 있는지도 확인할 수 있다. t 옵션을 사용하면 된다. 참고로 정적 라이브러리의 이름은 libNAME.a의 형식을 따라야 한다.
# ar t libmycalc.a
div.o
mul.o
sum.o
sub.o

그럼 정적라이브러리를 이용해서 실행파일을 만들어 보도록 하자. 이전에는 4개의 오브젝트 파일을 모두 링크시켜줘야 했지만, 이제는 libmycalc.a 만 링크시켜주면 된다.

라이브러리의 링크방식은 오브젝트를 링크하는 것과는 약간 차이가 있다. library의 위치를 명확히 명시해 주어야 한다. -L 옵션을 이용해서 라이브러리가 있는 디렉토리의 위치를 명시해주고, -l옵션을 이용해서, 라이브러리 파일의 이름을 정해줘야 한다. 다음은 simplecalc.c 를 정적라이브러리를 이용해서 컴파일하는 방법을 보여준다.
# gcc -o simplecalc simplecalc.c -L./ -lmycalc
-L./은 현재 디렉토리를 라이브러리 찾기 디렉토리로 하겠다는 의미가 된다. -l 옵션뒤에 붙이는 라이브러리 파일의 이름에 주목할 필요가 있다. 라이브러리 이름은 lib.a를 제외한 이름을 사용한다.
신고
Posted by remos
출처 : http://wiki.kldp.org/KoreanDoc/html/gcc_and_make/gcc_and_make-3.html#ss3.2

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 등 )의 간단/실전 사용법도 올려드릴까 생각 중입니다. ^^

신고
Posted by remos

ss

분류없음 2008.09.12 21:42
1. trasecode 해서 스트리밍 하는 방법 예제
    - 아래 방법은 rtp unicast로 스트리밍 하는 방법이고 아이피 주소는 스트리밍을 받을 host의 ip 주소를
    적어 주면 된다.

 vlc ./some.avi :sout='#transcode{vcodec=mp4v,vb=16,scale=1}:duplicate{dst=std{access=rtp,mux=ts,dst=128.134.65.196:1234}}'

2. 스트리밍 중에도 pause 등의 명령어가 가능하도록 하는 옵션
-I rc 혹은  --extraintf rc

vlc -I rc ./some.avi :sout='#transcode{vcodec=mp4v,vb=4,scale=1}:duplicate{dst=std{access=rtp,mux=ts,dst=128.134.65.196:1234}}'

3. transcode test
    -fps=1.50 부분.
    테스트 결과 fps가 2 이상이 되면 정상적으로 출력이 되며 2 이하로 주었을 때는 영상이 끊기는 느낌을 받는다. vb는 4480000을 주니 매우 깨끗하게 출력이  돼었다. 또한 test 결과 scale값(화면크기)의 변경이 영상의 끊김에 가장 큰 영향을 주었다.

 vlc -I rc ./some.avi :sout='#transcode{fps=1.50,vcodec=mp4v,vb=4480000,vbr=8,scale=1}:duplicate{dst=std{access=rtp,mux=ts,dst=128.134.65.196:1234}}'

어디서 퍼옴http://sec.tistory.com/search/vlc
신고
Posted by remos

dd

분류없음 2008.09.12 16:32

#  ./configure --enable-liba52  --enable-libfaad  --enable-libfaac  --enable-gpl  --enable-libx264  --enable-libxvid  --enable-pthreads  --enable-libvorbis  --enable-postproc  --enable-libtheora  --enable-libgsm  --enable-swscale   --enable-shared   --disable-debug   --prefix=/usr

신고
Posted by remos


티스토리 툴바