수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1620949
  • Today | 329
  • Yesterday | 482

22 Articles, Search for 'Unix & Linux/Develop'

  1. 2007/12/30 형식화된 입출력 – printf, fprintf, sprintf
  2. 2007/12/23 Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현
  3. 2007/11/27 Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)
  4. 2007/07/19 A Simple Makefile Tutorial
  5. 2007/05/14 세마포어의 사용
  6. 2007/05/14 cvs를 이용한 프로젝트 관리 (1)
  7. 2007/05/10 공유 라이브러리 이해하기
  8. 2007/05/10 컴포넌트를 안전하게 호출하기
  9. 2007/05/04 GDB를 이용한 Linux 소프트웨어의 디버깅
  10. 2007/05/04 객체 비지향(object disoriented)을 위한 공유 객체
«Prev  1 2 3  Next»
Unix & Linux/Develop2007/12/30 23:11

형식화된 입출력 – printf, fprintf, sprintf

printf, fprintf, sprintf

#include <stdio.h>

int printf(const char *format, …);

int sprintf(char *s, const char *format, …);

int fprintf(FILE *stream, const char *format, …);

출력 스트림에 각 인자를 표현하는 방법은 format 매개변수에 의해 조절된다. 이 매개변수는 출력할 일반적인 문자와 변환 지정자(conversion specifier)라고 불리는 코드를 포함하고 있다.

  • %d, %i: 정수를 십진 형태로 출력한다.

  • %o, %x: 정수를 8진수, 16진수 형태로 출력한다.

  • %c: 문자를 출력한다.

  • %s: 문자열을 출력한다.

  • %f: 부동 소수점(단일 정밀도) 숫자를 출력한다.

  • %e: 배정밀도(double precision) 숫자를 고정된 형식으로 출력한다.

  • %g: 배정밀도 숫자를 일반적인 형식으로 출력한다.

printf에 전달된 인자의 개수와 형식이 format 문자열에 있는 변환 지정자와 일치한다는 것은 매우 중요하다. 크기 지정자를 사용하여 정수 인자의 형식을 지정할 수도 있는데 없어도 된다. 크기 지정자는 h 혹은 l이 될 수 있다. 예를 들어 %hd는 short int를 나타내고, %ld는 long int를 나타낸다. 어떤 컴파일러는 이러한 printf 구문들을 검사할 수 있으나 절대 오류가 없을 수는 없다. GNU 컴파일러 gcc를 사용한다면 –Wformat이 이러한 검사를 수행해준다.

형식

인자

출력

%10s

“Hello”

         Hello

%-10s

“Hello”

Hello

%10d

1234

         1234

%-10d

1234

1234

%010d

1234

0000001234

%10.4f

12.34

     12.3400

%*s

10,”Hello”

         Hello

이 모든 예제는 10문자의 너비를 가진 필드에 출력된다. 음수 필드 너비는 항목이 필드 내에서 왼쪽 정렬됨을 의미한다. Asterisk(*)를 사용하여 변수 필드 너비를 지정한다. 이 경우에 다음 인자가 너비로 사용된다. 앞에 오는 0은 항목의 앞에 0이 쓰여짐을 의미한다. POSIX 명세에 따르면 printf는 필드의 내용을 지우지 않고 딱 맞도록 필드를 확장한다. 예를 들어 필드보다 긴 문자열을 출력하고자 시도한다면 필드는 커진다.

이 글을 출처에 해당하는 책에서 발췌하게 된 것은 Asterisk(*)를 이용해서 변수 필드 너비를 지정한다는 것을 이 책을 보고 처음 알았기 때문이다. 왜 다른 책에서는 볼 수 없었는지 의문이면서도 기능을 알게 된 것이 즐겁다.

출처 : Beginning Linux Programming 제3판 (정보문화사)

"Develop" 카테고리의 다른 글
  • 형식화된 입출력 – printf, fprintf, sprintf (0)2007/12/30
  • Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현 (0)2007/12/23
  • Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)2007/11/27
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
2007/12/30 23:11 2007/12/30 23:11
Posted by webdizen
Tags fprintf, printf, sprintf, 형식화된 입출력
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3160

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/12/23 00:12

Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현

리눅스를 사용하시고 있는 많은 사용자분들은 리눅스 콘솔에서 깔끔한 그래픽 표현을 보셨을 것이다.

바로 Dialog라는 유틸리티를 이용한 것이다. 다양한 매개변수와 옵션을 가지고 있는 하나의 프로그램이다.

간단한 예제를 통해서 사용방법을 살펴보도록 하자.

dialog --msgbox "Hello Dialog" 6 16

위와 같은 명령어를 콘솔에서 실행시키면 다음과 같은 화면을 볼 수 있다.

사용자 삽입 이미지

위 그림과 같이 정말 깔끔하게 표현되는 것을 볼 수 있다.


dialog 유틸리티에 다양한 옵션과 매개변수를 살펴 보도록 하자.

형식

옵션

매개변수

의미

체크 박스

--checklist

텍스트 높이 너비 목록-높이 [태그 텍스트 상태] …

항목의 목록을 표시한다. 각 항목들을 개별적으로 선택할 수 있다.

정보 상자

--infobox

텍스트 높이 너비

화면을 지우지 않고 즉각 반환하는 상자에 간단하게 표시한다.

입력 상자

--inputbox

텍스트 높이 너비 [초기 문자열]

사용자가 텍스트를 입력할 수 있다.

메뉴 상자

--menu

텍스트 높이 너비 메뉴-높이 [태그 항목] …

사용자가 목록으로부터 하나의 항목을 선택할 수 있다.

메시지 상자

--msgbox

텍스트 높이 너비

사용자에게 메시지를 표시한다. 사용자는 계속하고 싶을 때 OK 버튼을 누르면 된다.

라디오 상자

--radiolist

텍스트 높이 너비 목록-높이 [태그 텍스트 상태] …

사용자는 목록으로부터 한 가지를 선택할 수 있다.

텍스트 박스

--textbox

파일 이름 높이 너비

스크롤하는 상자 안에 파일을 표시할 수 있다.

예/아니오 상자

--yesno

텍스트 높이 너비

사용자에게 질문을 할 수 있다. 사용자는 Yes 혹은 No를 선택할 수 있다.


모든 dialog 형식은 몇 가지 옵션을 가지고 있다.

--title 옵션을 사용하면 상자의 제목을 지정하고, --clear 옵션을 사용하면 화면을 지울 수 있다.


다른 예제를 통해서 다양한 옵션과 매개변수를 사용해 보겠습니다.

dialog --title "Check Dialog" --checklist "Select Numbers" 14 26 3 1 "one" "off" 2 "two" "on" 3 "three" "off"

위와 같은 명령어를 콘솔에서 실행시키면 다음과 같은 화면을 볼 수 있다.

사용자 삽입 이미지

위 그림과 같이 --checklist 옵션을 사용해서 체크 박스를 표현할 수 있다.

--checklist 옵션의 매개변수를 살펴보면 다음과 같다.

텍스트 높이 너비 목록-높이 [태그 텍스트 상태] …

예제에 사용된 매개변수를 살펴보면 다음과 같다.

--checklist "Select Numbers" 14 26 3 1 "one" "off" 2 "two" "on" 3 "three" "off"

높이 : 14
너비 : 26
목록-높이 : 3
태그 텍스트 상태 : 1 "one" "off"
태그 텍스트 상태 : 2 "two" "on"
태그 텍스트 상태 : 3 "three" "off"

이렇게 쉽고 간단하게 콘솔에서 그래픽 표현이 가능한 dialog 유틸리티의 막강함을 볼 수 있다.


참고 도서 : Beginning Linux Programming 제3판

"Develop" 카테고리의 다른 글
  • 형식화된 입출력 – printf, fprintf, sprintf (0)2007/12/30
  • Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현 (0)2007/12/23
  • Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)2007/11/27
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
2007/12/23 00:12 2007/12/23 00:12
Posted by webdizen
Tags console, dialog, Linux
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3157

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/11/27 01:25

Vim에서 엉망이 된 소스 코드 자동으로 맞추기

출처 : http://special.homelinux.net/entry.php?blogid=158

정말 막강한 기능을 위 블로그를 통해서 알게 되었네요.
vim의 힘은 무궁무진 하네요. ^^


01. 엉망이 된 소스 코드를 vim으로 오픈합니다.

사용자 삽입 이미지

02. 엉망이 된 소스 코드 부분을 Shift+V 키를 이용하여 블록을 씌웁니다.

사용자 삽입 이미지

03. '=' 키 버튼을 클릭하면 다음과 같이 자동으로 맞춰집니다.

사용자 삽입 이미지

이런 기능 정말 효율적일꺼 같아요. 아름답네요 ^^
"Develop" 카테고리의 다른 글
  • 형식화된 입출력 – printf, fprintf, sprintf (0)2007/12/30
  • Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현 (0)2007/12/23
  • Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)2007/11/27
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
2007/11/27 01:25 2007/11/27 01:25
Posted by webdizen
Tags vim, 소스코드
No Trackback 2 Comments

Trackback URL : http://www.webdizen.net/blog/trackback/3144

Leave your greetings.

  1. adnoctum

    오호 이런 게 있었군요. 그렇잖아도 어제 이런 기능이 있으면 좋겠다, 라고 어떤 글에 답글을 달았었는데. 시기적절한 포스팅 감사합니다.

    2007/11/27 10:21 [ Permalink : Modify/Delete : Reply ]
  2. webdizen

    저도 이런 기능이 있는걸 보고 깜짝 놀랬었습니다. 출처에 해당하는 블로그에서 보고 나서 바로 적용해봤지용... ^^

    2007/11/27 22:32 [ Permalink : Modify/Delete : Reply ]
[로그인][오픈아이디란?]

Unix & Linux/Develop2007/07/19 08:34

A Simple Makefile Tutorial

출처 : http://palantir.swarthmore.edu/maxwell/ ··· tutor%2F

Makefiles are a simple way to organize code compilation. This tutorial does not even scratch the surface of what is possible using make, but is intended as a starters guide so that you can quickly and easily create your own makefiles for small to medium-sized projects.

A Simple Example

Let's start off with the following three files, hellomake.c, hellofunc.c, and hellomake.h, which would represent a typical main program, some functional code in a separate file, and an include file, respectively.

hellomake.c hellofunc.c hellomake.h
#include 

int main() {
// call a function in another file
myPrintHelloMake();

return(0);
}
#include 
#include

void myPrintHelloMake(void) {

printf("Hello makefiles!\n");

return;
}
/*
example include file
*/

void myPrintHelloMake(void);

Normally, you would compile this collection of code by executing the following command:

gcc -o hellomake hellomake.c hellofunc.c -I.

This compiles the two .c files and names the executable hellomake. The -I. is included so that gcc will look in the current directory (.) for the include file hellomake.h. Without a makefile, the typical approach to the test/modify/debug cycle is to use the up arrow in a terminal to go back to your last compile command so you don't have to type it each time, especially once you've added a few more .c files to the mix.

Unfortunately, this approach to compilation has two downfalls. First, if you lose the compile command or switch computers you have to retype it from scratch, which is inefficient at best. Second, if you are only making changes to one .c file, recompiling all of them every time is also time-consuming and inefficient. So, it's time to see what we can do with a makefile.

The simplest makefile you could create would look something like:

Makefile 1
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.

If you put this rule into a file called Makefile or makefile and then type make on the command line it will execute the compile command as you have written it in the makefile. Note that make with no arguments executes the first rule in the file. Furthermore, by putting the list of files on which the command depends on the first line after the :, make knows that the rule hellomake needs to be executed if any of those files change. Immediately, you have solved problem #1 and can avoid using the up arrow repeatedly, looking for your last compile command. However, the system is still not being efficient in terms of compiling only the latest changes.

One very important thing to note is that there is a tab before the gcc command in the makefile. There must be a tab at the beginning of any command, and make will not be happy if it's not there.

In order to be a bit more efficient, let's try the following:

Makefile 2
CC=gcc
CFLAGS=-I.

hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o -I.

So now we've defined some constants CC and CFLAGS. It turns out these are special constants that communicate to make how we want to compile the files hellomake.c and hellofunc.c. In particular, the macro CC is the C compiler to use, and CFLAGS is the list of flags to pass to the compilation command. By putting the object files--hellomake.o and hellofunc.o--in the dependency list and in the rule, make knows it must first compile the .c versions individually, and then build the executable hellomake.

Using this form of makefile is sufficient for most small scale projects. However, there is one thing missing: dependency on the include files. If you were to make a change to hellomake.h, for example, make would not recompile the .c files, even though they needed to be. In order to fix this, we need to tell make that all .c files depend on certain .h files. We can do this by writing a simple rule and adding it to the makefile.

Makefile 3
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h

%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)

hellomake: hellomake.o hellofunc.o
gcc -o hellomake hellomake.o hellofunc.o -I.

This addition first creates the macro DEPS, which is the set of .h files on which the .c files depend. Then we define a rule that applies to all files ending in the .o suffix. The rule says that the .o file depends upon the .c version of the file and the .h files included in the DEPS macro. The rule then says that to generate the .o file, make needs to compile the .c file using the compiler defined in the CC macro. The -c flag says to generate the object file, the -o $@ says to put the output of the compilation in the file named on the left side of the :, the $< is the first item in the dependencies list, and the CFLAGS macro is defined as above.

As a final simplification, let's use the special macros $@ and $^, which are the left and right sides of the :, respectively, to make the overall compilation rule more general. In the example below, all of the include files should be listed as part of the macro DEPS, and all of the object files should be listed as part of the macro OBJ.

Makefile 4
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o

%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS)

So what if we want to start putting our .h files in an include directory, our source code in a src directory, and some local libraries in a lib directory? Also, can we somehow hide those annoying .o files that hang around all over the place? The answer, of course, is yes. The following makefile defines paths to the include and lib directories, and places the object files in an obj subdirectory within the src directory. It also has a macro defined for any libraries you want to include, such as the math library -lm. This makefile should be located in the src directory. Note that it also includes a rule for cleaning up your source and object directories if you type make clean. The .PHONY rule keeps make from doing something with a file named clean.

Makefile 5
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

LIBS=-lm

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))


$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS) $(LIBS)

.PHONY: clean

clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~

So now you have a perfectly good makefile that you can modify to manage small and medium-sized software projects. You can add multiple rules to a makefile; you can even create rules that call other rules. For more information on makefiles and the make function, check out the GNU Make Manual, which will tell you more than you ever wanted to know (really).

"Develop" 카테고리의 다른 글
  • Dialog 유틸리티를 이용한 콘솔에서의 그래픽 표현 (0)2007/12/23
  • Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)2007/11/27
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
  • cvs를 이용한 프로젝트 관리 (2)2007/05/14
2007/07/19 08:34 2007/07/19 08:34
Posted by webdizen
Tags gcc, Makefile
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3081

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/14 17:29

세마포어의 사용

세마포어는 System V IPC 의 설비중 하나로, 하나의 자원에 여러개의 프로세스가 접근하려고 할때, 해당자원에 대한 접근제어를 하기 위해 필요한 설비이다.
이번 강좌를 통해서 세마포어의 사용방법에 대해서 알아보도록 하겠다.


세마포어란 무엇인가
세마포어(Semaphores)를 비록 IPC설비중의 하나로 분류하긴 했지만, 다른 파이프, 메시지큐, FIFO 등과는 좀다르다. 다른 IPC 설비들이 대부분 프로세스간 메시지 전송을 그 목적으로 하는데 반해서 세마포어는 프로세스간 데이타를 동기화 하고 보호하는데 그목적이 있다.

프로세스간 메시지 전송을 하거나, 혹은 공유메모리를 통해서 특정 데이타를 공유하게 될경우 발생하는 문제가, 공유된 자원에 여러개의 프로세스가 동시에 접근을 하면안되며, 단지 한번에 하나의 프로세스만 접근 가능하도록 만들어줘야 할것이다. 이것은 쓰레드에서 메시지간 동기화를 위해서 mutex 를 사용하는것과 같은 이유이다.

하나의 데이타에 여러개의 프로세스가 관여할때 어떤 문제점이 발생할수 있는지 간단한 예를 들어보도록 하겠다. int count=100;
A 프로세스가 count 를 읽어들인다.     100
B 프로세스가 count 를 읽어들인다.     100
B 프로세스가 count 를 1 증가 시킨다.  101
A 프로세스가 count 를 1 증가 시킨다.  101


count 는 공유자원(공유메모리 같은)이며 A와 B 프로그램이 여기에 대한 작업을 한다. A가 1을 증가 시키고 B가 1을 증가시키므로 최종 count 값은 102 가 되어야 할것이다. 그러나 A 가 작업을 마치기 전에 B가 작업을 하게 됨으로 엉뚱한 결과를 보여주게 되었다. 위의 문제를 해결하기 위해서는 count 에 A가 접근할때 B프로세스가 접근하지못하도록 block 시키고, A가 모든 작업을 마쳤을때 B프로세스가 작업을 할수 있도록 block 를 해제 시키면 될것이다.
우리는 세마포어를 이용해서 이러한 작업을 할수 있다. 한마디로 줄여서 세마포어는 "여러개의 프로세스에 의해서 공유된는 자원의 접근제어를 위한 도구" 이다.
세마포어의 작동원리
작동원리는 매우 간단하다. 차단을 원하는 자원에대해서 세마포어를 생성하면 해당자원을 가리키는 세마포어 값이 할당된다. 이 세마포어 값에는 현재 세마포어를 적용하고 있는 자원에 접근할수 있는 프로세스의 숫자를 나타낸다. 이 값이 0이면 이 자원에 접근할수 있는 프로세스의 숫자가 0이라는 뜻이며, 자원), 0보다 큰 정수면 해당 정수의 크기만큼의 프로세스가 자원에 접근할수 있다라는 뜻이 된다. 그러므로 우리는 접근제어를 해야하는 자원에 접근하기 전에 세마포어 값을 검사해서 값이 0이면 자원을 사용할수 있을때까지 기다리고, 0보다 더크면(1이라고 가정하자) 자원에 접근해서 세마포어 값을 1 감소 시켜서, 세마포어 값을 0으로 만들어서, 다른 프로세스가 자원에 접근할수 없도록 하고, 자원의 사용이 끝나면 세마포어 값을 다시 1증가시켜서 다른 프로세스가 자원을 사용할수 있도록 만들어주면 된다.

만약 세마포어 값을 검사했는데 세마포어 값이 0이라면 사용할수 있게 될때까지 (1이 될때까지) 기다리면 (block) 될것이다.
세마포어의 사용
세마포어의 사용은 위의 작동원리를 그대로 적용한다. 즉 1. 세마포어로 제어할 자원을 설정한다.
2. 해당 자원을 사용하기전에 세마포어 값을 확인한다.
3. 세마포어 값이 0보다 크면 자원을 사용하고, 세마포어 값을 1 감소 시킨다.
4. 세마포어 값이 0이면 값이 0보다 커질때까지 block 되며, 0보다 커지게 되면 2번 부터 시작하게 된다.

위의 작업을 위해서 Unix 는 다음과 같은 관련함수들을 제공한다.



세마포어의 관리
세마포어는 그 특성상 원자화된 연산을 필요로 한다. 이러한 원자화된 연산은 유저레벨의 함수에서는 제공하기가 힘들므로, 세마포어 정보는 커널에서 전용 구조체를 이용해서 관리하게 된다. 다음은 커널에서 세모포어 정보를 유지하기 위해서 관리하는 구조체인 semid_ds 구조체의 모습이다. semid_ds 는 /usr/include/bits/sem.h 에 선언되어 있다. (이것은 리눅스에서의 경우로 Unix 버젼에 따라서 위치와 멤버변수에 약간씩 차이가 있을수 있다)


sem_perm 은 세마포어에 대한 퍼미션으로 일반 파일퍼미션과 마찬가지의 기능을 제공한다. 즉 현재 세마포어 구조체에 접근할수 있는 사용자권한을 설정한다. sem_nsems 는 생성할수 있는 세마포어의 크기이다. sem_otime 은 마지막으로 세마포어관련 작업을 한 시간(semop 함수를 이용)이며, sem_ctim 은 마지막으로 구조체 정보가 바뀐 시간이다.
semget 을 이용해서 세마포어를 만들자.
세마포어의 생성혹은 기존에 만들어져 있는 세마포어에 접근하기 위해서 유닉스에서 는 semget(2)를 제공한다. 첫번째 아규먼트는 세마포어의 유일한 키값을 위해서 사용하는 int 형의 키값이다. 우리는 이 key 값을 이용해서 유일한 세마포어를 생성하거나 접근할수 있게 된다. 새로 생성되거나 기존의 세마포어에 접근하거나 하는것은 semflg 를 통해서 제어할수 있다. 다음은 semflg 의 아규먼트이다.


IPC_CREAT
만약 커널에 해당 key 값으로 존재하는 세마포어가 없다면, 새로 생성 한다.
IPC_EXCL
IPC_CREAT와 함께 사용하며, 해당 key 값으로 세마포어가 이미 존재한다면 실패값을 리턴한다.
semflg 를 통해서 세마포어에 대한 퍼미션을 지정할수도 있다. 퍼미션 지정은 보통의 파일에 대해서 유저/그룹/other 에 대해서 지정하는것과 같다.

만약 IPC_CREAT 만 사용할경우 해당 key 값으로 존재하는 세마포어가 없다면, 새로 생성하고, 이미 존재한다면 존재하는 세마포어의 id 를 넘겨준다. IPC_EXCL을 사용하면 key 값으로 존재하는 세마포어가 없을경우 새로 생성되고, 이미 존재한다면 존재하는 id 값을 돌려주지 않고 실패값(-1)을 되돌려주고, errno 를 설정한다.

nsems 은 세마포어가 만들어질수 있는 크기이다. 이값은 최초 세마포어를 생성하는 생성자의 경우에 크기가 필요하다(보통 1). 그외에 세마포어에 접근해서 사용하는 소비자의 경우에는 세마포어를 만들지 않고 단지 접근만 할뿐임으로 크기는 0이 된다.

이상의 내용을 정리하면 semget 은 아래와 같이 사용할수 있을것이다. 만약 최초 생성이라면
   sem_num = 1;
그렇지 않고 만들어진 세마포어에 접근하는 것이라면
   sem_num = 0;
sem_id = semget(12345, sem_num, IPC_CREAT|0660)) == -1)
{
   perror("semget error : ");
   return -1;
}


semget 은 성공할경우 int 형의 세마포어 지사자를 되돌려주며, 모든 세마포어에 대한 접근은 이 세마포어 지시자를 사용하게 된다.

위의 코드는 key 12345 를 이용해서 세마포어를 생성하며 퍼미션은 0660으로 설정된다. 세마포어의 크기는 1로 잡혀 있다(대부분의 경우 1). 만약 기존에 key 12345 로 이미 만들어진 세마포어가 있다면 새로 생성하지 않고 기존의 세마포어에 접근할수 있는 세마포어 지시자를 되돌려주게 되고, 커널은 semget 를 통해 넘어온 정보를 이용해서 semid_ds 구조체를 세팅한다.


예제: semget.c



이제 위의 코드를 컴파일해서 실행시키고 나서 실제로 세마포어 정보가 어떻게 바뀌였는지 확인해 보도록 하자.

커널에서 관리되는 ipc 정보를 알아보기 위해서는 ipcs(8)라는 도구를 이용하면 된다.
[root@localhost test]# ipcs -s    
------ Semaphore Arrays --------
key        semid      owner      perms      nsems      status      
0x00003039 0          root      666        1        


0x00003039 은 key 12345 의 16진수 표현이다. 퍼미션은 666으로 되어 있고 semget 를 통해서 제대로 설정되어 있음을 알수 있다.
세마포어를 이용해서 접근제어 하기
이제 semget 을 통해서 세마포어를 만들거나 접근했으니, 이제 실제로 세마포어상태를 검사해서 접근제어를 해보도록하자.

이것은 semop를 통해서 이루어진다.

semop 의 첫번째 semid 는 semget 을 통해서 얻은 세마포어 지시자이다. 2번째 아규먼트는 struct sembuf 로써, 어떤 연산을 이루어지게 할런지 결정하기 위해서 사용된다. 구조체의 내은 다음과 같으며, sys/sem.h 에 선언되어 있다.



sem_num 멤버는 세마포어의 수로 여러개의 세마포어를 사용하지 않는다면(즉 배열이 아닐경우) 0을 사용한다. 배열의 인덱스 사이즈라고 생각하면 될것이다. 보통의 경우 하나의 세마포어를 지정해서 사용하므로 0 이 될것이다.
sem_op 를 이용해서 실질적으로 세마포어 연산을 하게 되며, 이것을 이용해서 세마포어 값을 증가시키거나 감소 시킬수 잇다. sem_op 값이 양수일 경우는 자원을 다 썼으니, 세마포어 값을 증가시키겠다는 뜻이며, 음수일 경우에는 세마포어를 사용할것을 요청한다라는 뜻이다. 음수일 경우 세마포어값이 충분하다면 세마포어를 사용할수 있으며, 커널은 세마포어의 값을 음수의 크기의 절대값만큼을 세마포어에서 빼준다. 만약 세마포어의 값이 충분하지 않다면 세번째 아규먼트인 sem_flg 에 따라서 행동이 결정되는데,
sem_flg 가 IPC_NOWAIT로 명시되어 있다면, 해당영역에서 기다리지 않고(none block) 바로 에러코드를 리턴한다. 그렇지 않다면 세마포어를 획득할수 있을때까지 block 되게 된다. sem_flg 는 IPC_NOWAIT 와 SEM_UNDO 2개의 설정할수 있는 값을가지고 있다. IPC_NOWAIT 는 none block 모드 지정을 위해서 사용되며, SEM_UNDO 는 프로세스가 세마포어를 돌려주지 않고 종료해버릴경우 커널에서 알아서 세마포어 값을 조정(증가) 할수 있도록 만들어 준다.

설명이 아마 애매모호한면이 있을것이다. 간단한 상황을 예로 들어서 설명해 보겠다. 현재 세마포어 값이 1 이라고 가정하자.
이때 A 프로세스가 semop 를 통해서 세마포어에 접근을 시도한다.
A는 접근을 위해서 sem_op 에 -1 을 세팅한다. 즉 세마포어 자원을 1 만큼 사용하겠다라는
뜻이다.  
현재 준비된 세마포어 값은 1로 즉시 사용할수 있으므로,
A는 자원을 사용하게 되며, 커널은 세마포어 값을 1 만큼 감소시킨다.

이때 B 라는 프로세스가 세마포어 자원을 1 만큼 사용하겠다라고 요청을 한다.
그러나 지금 세마포어 값은 0 이므로 B는 지금당장 세마포어 를 사용할수 없으며,
기다리거나, 에러값을 리턴 받아야 한다(IPC_NOWAIT).  
B는 자원 사용가능할때까지 기다리기로 결정을 했다.  

잠수후 A는 모든 작업을 다마쳤다.
이제 세마포어를 되돌려줘야 한다. sem_op 에 1 을 세팅하면,
커널은 세마포어 값을 1증가시키게 된다.

드디어 기다리던 B가 세마포어 자원을 사용할수 있는 때가 도래했다.
이제 세마포어 값은 1이 므로 B는 세마포어를 획득하게 된다.  
커널은 세마포어 값을 1 감소 시킨다.
B는 원하는 작업을 한다.
...
...



세마포어 조작
semctl 이란 함수를 이용해서 우리는 세마포어를 조정할수 있다. semctl 은 semid_ds 구조체를 변경함으로써 세마포어의 특성을 조정한다.

첫번째 아규먼트인 semid 는 세마포어 지시자이다. semnum 은 세마포어 배열을 다룰 경우 사용되며, 보통은 0이다. cmd 는 세마포어 조작명령어 셋으로 다음과 같은 조작명령어들을 가지고 있다. 아래는 그중 중요하다고 생각되는 것들만을 설명하였다. 더 자세한 내용은 semctl 에 대한 man 페이지를 참고하기 바란다.
IPC_STAT
세마포어 상태값을 얻어오기 위해 사용되며, 상태값은 arg 에 저장된다.
IPC_RMID
세마포어 를 삭제하기 위해서 사용한다.
IPC_SET
semid_ds 의 ipc_perm 정보를 변경함으로써 세마포어에 대한 권한을 변경한다.
예제를 통한 이해
지금까지 익혔던 내용을 토대로 간단한 예제프로그램을 만들어보겠다. 예제의 상황은 하나의 파일에 2개의 프로세스가 동시에 접근하고자 하는데에서 발생한다. 파일에는 count 숫자가 들어 있으며, 프로세스는 파일을 열어서 count 숫자를 읽어들이고, 여기에 1을 더해서 다시 저장하는 작업을한다. 이것을 세마포어를 통해서 제어하지 않으면 위에서 설명한문제가 발생할것이다.

위의 문제를 해결하기 위해서는 파일을 열기전에 세마포어를 설정해서 한번에 하나의 프로세스만 접근가능하도록 하면 될것이다. 모든 파일작업을 마치게 되면, 세마포어 자원을 돌려줌으로써, 비로서 다른 프로세스가 접근가능하게 만들어야 한다.
예제: sem_test




코드는 매우 간단하지만, 세마포어에 대한 기본적인 이해를 충분히 할수 있을만한 코드이다. 생성자와 소비자의 분리는 프로그램에 넘겨지는 아규먼트를 이용했다. 모든 작업을 마치면 테스트를 위해서 10초를 기다린후에 세마포어를 돌려주도록 코딩되어 있다.

우선 count 를 저장할 파일 counter.txt 를 만들고 여기에는 1을 저장해 놓는다. 그다음 ./sem_test 를 실행시키는데, 최초에는 생성자를 만들어야 하므로 아규먼트를 주어서 실행시키고, 그다음에 실행시킬때는 소비자가 되므로 아규먼트 없이 실행하도록 하자. 다음은 테스트 방법이다.
[root@coco test]# ./sem_test 1&
[1] 3473
36
[root@coco test]# ./sem_test


위 코드를 실행해보면 ./sem_test 1 이 세마포어자원을 돌려주기 전까지 ./sem_test 가 해당영역에서(세마포어 요청하는 부분) 블럭되어 있음을 알수 있고, 충돌없이 count가 잘되는것을 볼수 있을것이다.

세마포어는 커널에서 관리하는데 세마포어를 사용하는 프로세스가 없다고 하더라도 semctl 을 이용해서 제거하지 않는한은 커널에 남아있게 된다. 세마포어 정보를 제거하기 위해서는 semctl 연산을 하든지, 컴퓨터를 리붓 시커거나, ipcrm(8)이란 도구를 사용해서 제거시켜 줘야 한다.
결론
세마포어는 fcntl 과 매우 비슷한 일을 수행하는데, 좀더 세밀한 조정이 가능하다라는 장점을 가지고 있지만 간단한 일을 하기엔 지나치게 복잡한 면이 없잖아 있다. 이럴경우에는 세마포어 대신 fcntl 을 사용하자.
fcntl 은 기회가 되는데로 다루도록 하겠다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Develop" 카테고리의 다른 글
  • Vim에서 엉망이 된 소스 코드 자동으로 맞추기 (2)2007/11/27
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
  • cvs를 이용한 프로젝트 관리 (2)2007/05/14
  • 공유 라이브러리 이해하기 (0)2007/05/10
2007/05/14 17:29 2007/05/14 17:29
Posted by webdizen
Tags Semapores, 세마포어
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2946

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/14 17:22

cvs를 이용한 프로젝트 관리

규모가 좀 있는 프로젝트의 경우 2,3명 혹은 그 이상이 공동으로 작업할때가 있다. 이럴경우 각 프로젝트의 자원들 (문서, 쏘쓰, 버젼)등을 관리할 필요가 있다. 이번에는 이러한 프로젝트관리를 도와주는 CVS 에 대해서 알아보도록 하겠다.


1절. CVS에 대한 소개
2절. CVS에 대한 기본지식
2.1절. CVS 란?
2.2절. 어떨때 CVS가 필요하죠?
2.2.1절. 공동 프로젝트 관리
2.2.2절. 프로젝트 백업
2.2.3절. 데이터 동기화
3절. CVS 사용하기
3.1절. CVS 서버 설치하기
3.1.1절. CVS저장소 만들기
3.1.2절. CVS 유저 환경 설정
3.1.3절. CVS 사용하기
3.1.3.1절. 익명 CVS설정하기
3.1.4절. CVS 사용자 환경설정
3.2절. cvs 클라이언트 사용하기
3.2.1절. 로그인 하기 : login
3.2.2절. 프로젝트 만들고 등록하기 : import
3.2.3절. 프로젝트 가져오기 : checkout
3.2.4절. 프로젝트 수정후 적용(업데이트) : commit
3.2.5절. 파일 받아오기/로그 보기 : update, log
3.2.6절. 버젼별 차이 확인 : diff
3.2.7절. 파일 추가하기 : add
3.2.8절. 충돌의 해결
3.2.9절. 필요없는 파일지우기 : delete
3.2.10절. 프로젝트를 완료했을때 : release
3.3절. 효율적인 프로젝트 관리를 위한 CVS 사용법
4절. CVS 의 다른 활용들
5절. 결론

--------------------------------------------------------------------------------

1절. CVS에 대한 소개
과거는 컴퓨터 시스템의 능력이 매우 제한적이였고 사용하는 유저 역시 제한적이거나 지극히 개인적인 용도로 사용하는 경우가 대부분 이였다. 때문에 소프트웨어역시 비교적 단순했으며 단지 한두명의 개발자 투입만으로도 꽤나 쓸만한 애플리케이션이 만들어지기도 했다.

도스 시절부터 컴퓨터를 다루어왔던 유저라면 "누구누구의 어떤 프로그램" 이라는 얘기를 많이 들어 보았을 것이다. 지금은 사정이 달라져서 아주 간단한 프로그램이 아니고서는 혼자 개발해서 그럭저럭 인지도 있는 프로그램을 만든다는것 자체가 매우 힘들어졌다.

요즘은 UI만 제작하는 것도 하나의 작업으로 분류된다. 인터넷이 일반적으로 보급되면서 대부분의 애플리케이션은 C/S환경하에서 작동하게 된다. 개발자는 데이터전송과 처리에 대한 부분까지 신경써야 하며 많은 경우 서버와 클라이언트가 서로 다른 운영체제하에 놓이며, 특히 서버 프로그램의 경우 여러가지의 전혀 달라보이는 운영체제를 지원해야하는 경우도 있다.

이런 이유로 왠만한 규모의 프로젝트라 할지라도 혼자서 개발을 진행한다는건 매우 힘들며, 대부분 팀단위로 프로젝트를 진행을 하게 된다. 이 팀이란건 또 어떤가 같은 사무실에서 같은 시간에 존재하면서 서로 의견교환을 통해서 프로젝트를 진행 시킬 수도 있지만 인터넷이라는 매체를 통해서 전혀 다른 공간에서 전혀 다른 시간대에 프로젝트를 진행 시켜야 하는 경우도 생긴다. 전 세계인이 참여하는 많은 오픈 프로젝트가 그러하다.

이런 경우 하나의 소스코드를 한명 이상이 접근해서 수정할 수 있는데, 프로젝트를 진행하다 보면 소스코드가 엉뚱하게 꼬일 수 있을 것이다. 같은 사무실에서 단지 몇명의 프로그래머가 작업을 한다면, 서로 의견조율을 하거나 처음부터 각각의 모듈만 담당하게 만들어서 어느정도 문제를 해결할 수 있을 거라고 생각할 수도 있을 것이다. 그러나 막상 프로젝트를 진행해보면 이게 결코 말처럼 쉬운일이 아니란걸 알게 될것이다. 프로젝트를 진행하다 보면 이쪽 시스템에서 테스트하고, 저쪽 시스템에서 테스트하고 문제가 생기면 즉각 수정을 하게 되는데, 이러다보면 소스코드가 여기저기 위치하게 되고 결국 어느 소스가 최근 소스코드인지 헷갈리는 사태가 발생하게 된다. 하물며 오픈소스와 같이 수많은 프로그래머가 느슨하게 묶여있는 경우는 더 말할 필요도 없다. 적당한 버젼관리 도구의 사용없이는 프로젝트의 진행자체가 불가능해질 것이다.

이러한 문제의 해결을 위해서 여러도구가 개발되었는데, 그중 하나가 CVS로 현재 가장 널리 사용되고 있는 버젼관리 도구이다.


--------------------------------------------------------------------------------

2절. CVS에 대한 기본지식
2.1절. CVS 란?
CVS는 Concurrent Version System 의 줄임말로써 직역 하자면 공동 버젼 시스템, 의역하자면 "공동으로 진행하는 프로젝트의 버젼 관리 시스템" 정도가 될것이다.


--------------------------------------------------------------------------------

2.2절. 어떨때 CVS가 필요하죠?
CVS는 사용하기에 따라서 여러가지 용도로 사용할수 있다. 이번장에서는 CVS를 이용할수 있는 다양한 상황들에 대해서 알아보도록 하겠다.


--------------------------------------------------------------------------------

2.2.1절. 공동 프로젝트 관리
회사혹은 학교에서 프로젝트를 진행하다보면, 여러명이서 하나의 프로젝트를 진행하는 경우가 발생할것이다.

이럴경우 보통 모듈별로 개발을 하게 되겠지만, 또한 모듈은 전체 프로젝트에 영향을 미치게 되므로, 자신의 모듈버젼과 전체 프로젝트의 버젼을 컨트롤할수 있어야 한다. 그리고 코드가 충동하게 될경우(서로 같은 부분을 수정함으로써)의 문제를 해결할수 있어야 한다.

이러한 작업은 프로젝트 규모가 작고 개발 참여자 수가 적고, 개발 참여자가 가까운 지역(사무실 같은)에 모두 모여있다면, 한명의 버젼관리자(보통은 팀장)를 두고 그럭저럭 관리가 가능할것이다.

그러나 조금만 프로젝트가 커지고, 개발참여자 수가 많아지고 개발자가 지역적으로 떨어져 있는 상황에서는 거의 불가능 하다는 걸 알수 있게 될것이다. 특히 인터넷을 통해서 느슨하게 연결된 오픈 프로젝트의 경우 도구를 사용하지 않는 다면 거의 관리가 불가능 할 것이다.

CVS 를 사용하면 이러한 대규코의 프로젝트에서 각 모듈 개발자가 자신의 버젼을 유지하면서 전체 프로젝트에 참여할수 있도록 할수 있다.

실제로 KDE, GNOME, APACHE 서버 등 다양한 프로젝트가 CVS 를 이용해서 프로젝트를 관리하고 있다. 이러한 프로젝트는 규모가 작아도 수십명, 혹은 수백명이 프로젝트를 진행하게 되는데(게다가 지역적으로 멀리 떨어져 있다), CVS가 중간에서 프로젝트가 산으로 가지 않도록 중계해준다. 오픈 프로젝트를 하는데 있어서 CVS는 거의 표준적으로 사용되는 버젼관리 도구이다.


--------------------------------------------------------------------------------

2.2.2절. 프로젝트 백업
CVS 를 사용할경우 자동적으로 프로젝트 백업의 문제까지 해결이 가능하다. CVS 는 중간에 CVS 서버가 있어서, 프로젝트 데이터의 저장소 역할을하며 모든 개발자는 CVS 서버에서 최신의 프로젝트를 다운로드 받아서, 자신의 컴퓨터에서 테스트하고 코딩해서, 이걸 다시 CVS 서버에 업데이트 시키는 방식을 사용하게 된다.

그러므로 실수로 자신의 프로젝트 데이터가 날아간다고 해도 전혀 염려할 필요가 없다. 그냥 서버에서 다시 다운 받기만 하면 된다. 최악의 경우 CVS 서버가 날라갔다고 하더라도 가장 최근의 쏘쓰를 가진 개발자가 있을 것이므로 쉽게 복구 가능하다.

또한 CVS는 최신 버젼의 소스코드 뿐만 아니라 과거 버젼의 소스코드에 대한 정보를 가지고 있어서 최근의 몇개 버젼에 문제가 생겼다고 하더라도 쉽게 그이전의 소스코드를 얻어올 수 있다.


--------------------------------------------------------------------------------

2.2.3절. 데이터 동기화
요즘은 회사와 집과의 경계가 많이 허물어 졌다. (좋은 현상인지 나쁜 현상인지는 좀 생각해 봐야겠지만) 그러다 보니 회사에서 하는일을 가정에서 하기도 하고, 가정에서 했던 일을 회사로 가져가기도 한다. 그럴경우 회사의 컴터와 가정의 컴터에 있는 데이타의 동기화가 필수적이다. 이런 데이타 동기화를 위해서 "노트북", "PDA" 같은걸 사용할수 있겠지만, 이건 너무 비싸다. ftp 도 사용할수 있겠지만, 이거 잘못 사용하면 데이터가 꼬일수 있다. 또한 상당히 불편하다.

이럴때 CVS 를 사용하면 대단히 편하게 작업이 가능하다. 회사에서 작업을 마치고 CVS 서버에 등록하고, 가정으로 돌아가서 CVS 서버에 등록된 최신의 작업을 받아와서 작업을 하고 다시 CVS 서버에 등록만 하면 되기 때문이다. (물론 이왕이면 가정에까지 회사일을 가지고 가지 않으면 좋겠지만..)

또한 덤으로 자신의 중요한 자료까지 자동으로 백업된다.

필자 역시 이러한 방법으로 작업을 한다. 작업거리가 좀 남았는데, 회사에서는 일이 잘안되고(실은 일하기 싫어서겠지만 --;), 그냥 집에가서 느긋하게 TV도 보고, 웃통 벗어던지고 일하고 싶을때 매우 편하게 CVS를 이용할수 있다.


--------------------------------------------------------------------------------

3절. CVS 사용하기
3.1절. CVS 서버 설치하기
지금 까지 CVS에 대한 개론적인 설명을 알아 보았다. 이제 본격적으로 CVS를 설치하고 운용하고 사용하는 방법을 알아보도록 하겠다. CVS 서버 설치는 redhat 8.x 리눅스를 기준으로 하겠다. 대부분의 redhat 리눅스 배포판은 cvs 를 기본적으로 포함하고 있다. rpm 패키지 관리자를 통해서 설치하자.

이제 Internet services daemon 에 cvs 서버를 등록 시켜주기만 하면 된다. 레드헷 7.x 버젼부터는 inetd 대신에 xinetd 가 Internet services daemon 으로 사용되어 지고 있다. /etc/xinetd.d 디렉토리 밑에 cvspserver 이란 이름으로 서비스 설정파일을 만들도록 하자. 내용은 다음과 같다.

service cvspserver
{
disable = no
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/bin/cvs
server_args = --allow-root=/home/cvs pserver
log_on_failure += USERID
}


만약 inetd를 사용하는 예전 버젼의 리눅스라면 아래와같이 설정하도록 한다. # echo "2401 stream tcp nowait root /usr/bin/cvs cvs -f \
--allow-root=/cvsroot pserver" >> /etc/inetd.conf




위의 설정값들중 --allow-root를 주목하기 바란다. 프로젝트가 서버에 저장된다면 당연히 이들 프로젝트가 서버의 어디 디렉토리로 저장되어야 하는지 지정할 수 있어야 할것이다. cvs에서는 이를 repository(이하 저장소)라고 한다. --allow-root는 cvs 저장소가 /home/cvs임을 명시해 주기 위해서 사용된다. 만약 새로운 프로젝트인 hello_world를 만들었다면 이 프로젝트는 /home/cvs/hello_world 디렉토리에 저장이 된다.

이제 xinetd 데몬(혹은 inetd데몬)을 다시 실행 시키면 cvs 서버가 작동하게 될것이다. cvs 서비스는 2401 포트를 이용해서 서비스 된다(/etc/services 참조). 제대로 cvs 서비스가 되고 있는지 알아보기 위해서 포트 스캐닝 도구인 nmap 을 사용해서 확인해 보도록 하자. [root@cvs xinetd.d]# nmap 192.168.0.4

Starting nmap V. 2.54BETA7 ( www.insecure.org/nmap/ )
Interesting ports on localhost.localdomain (127.0.0.1):
(The 1527 ports scanned but not shown below are in state: closed)
Port State Service
22/tcp open ssh
25/tcp open smtp
80/tcp open http
111/tcp open sunrpc
2401/tcp open cvspserver
3306/tcp open mysql


2401 번 포트로 cvspserver 가 서비스 되고 있음을 알수 있다. 이로써 cvs 서버의 설치및 가동을 마쳤다.

다른 배포판을 사용하더라도 설치상에 있어서 문제점은 없을것이다. 데비안 이라면 전용 패키지관리자를 이용해서 설치하면 된다. 패키지 설치가 여의치 않다면 쏘쓰를 직접 컴파일 해서 설치 하면 된다.

cvs 는 cvspserver 를 이용한 서비스 외에도 rsh, ssh 를 이용한 서비스도 가능하다. 이에 대한 내용은 CVS 사용 문서를 참고 하기 바란다. 이문서에서는 가장 널리 사용되는 cvspserver 방식에 대해서만 설명하고 있다.


--------------------------------------------------------------------------------

3.1.1절. CVS저장소 만들기
위의 설정에서 우리는 /home/cvs를 프로젝트들을 위한 저장소로 사용한다고 했는데, 저장소로 사용하기 전에 저장소 터를 다지기 위한 사전작업이 필요하다.

이러한 작업은 cvs에서 제공하는 init옵션을 통해서 가능하다.
# cvs -d /home/cvs init


-d를 이용해서 저장소로 사용될 디렉토리를 지정하고 init를 명시하는 정도로 어렵잖게 저장소를 생성할 수 있다.


--------------------------------------------------------------------------------

3.1.2절. CVS 유저 환경 설정
CVS 에 서버를 만들어 놓았으면 이제 CVS 자원을 사용하도록 환경설정을 해주어야 한다. 가장 중요한 건 공동으로 작업할 프로젝트 파일들이 저장될 CVS 저장 디렉토리(저장소)를 설정하는 일이다.

일단 우리는 위의 /etc/xinetd.d/cvspserver 를 설정하면서 cvs 데몬이 뜨게될경우 --allow-root 옵션을 이용해서 /home/cvs 를 홈디렉토리(프로젝트가 저장될 디렉토리)를 지정했다. 이제 /home/cvs 를 실제 프로젝트 사용자들이 사용할수 있도록 권한 설정을 해주어야 한다.

cvs 의 권한 설정을 위해서 cvs 란 그룹을 만들도록 하고 /home/cvs 디렉토리에 cvs 그룹에 대해서 읽기/쓰기/실행 권한을 부여하도록 하자.
# groupadd cvs
# mkdir cvs
# chmod 770 cvs


이제 cvs 그룹에 포함된 모든 사용자는 CVS 자원을 이용할수 있는 권한을 가지게 되며, 이후로는 프로젝트를 등록시켜서 공동작업에 CVS 를 이용하기만 하면 된다.


--------------------------------------------------------------------------------

3.1.3절. CVS 사용하기
이제 CVS 서버의 설정이 끝났음으로, 클라이언트의 입장에서 어떻게 프로젝트를 등록하고 공동으로 작업을 진행시킬수 있는지에 대해서 알아보도록 하겠다. 이러한 작업들은 클라이언트에게 제공되는 "cvs" 라는 프로그램을 통해서 이루어진다.

CVS 의 사용방법은 다음과 같은 환경하에서 테스트되었다. +------------+
| @cvs | project : hello_world
| CVS SERVER |
+------------+
|
|
+------------+------------+
| |
+---------+ +---------+
| @myhome | | @one |
+---------+ +---------+

@cvs 는 CVS 저장소를 가지고 있는 서버이며, @myhome 은 "팀원" @one 는 "팀장" 의 개발호스트(컴퓨터) 이다. 공동으로 진행될 프로젝트는 hello_world 이다.


--------------------------------------------------------------------------------

3.1.3.1절. 익명 CVS설정하기
익명 CVS란 말그대로 일반 사용자에게 CVS를 읽고/쓸수 있는 권한을 부여하는 것이며, 많은 오픈 프로젝트들이 익명 CVS를 허용해서 가능한한 많은 개발자가 참여할 수 있도록 길을 열어 놓고 있다.

이러한 익명 CVS사용자는 보통 프로젝트에 대한 읽기권한만을 부여한다. 익명 사용자에게 쓰기권한을 주면 프로젝트의 진행이 너무 산만해 질 수 있기 때문이다. 익명 사용자(혹은 개발자)는 프로젝트에 반영해야 될 내용이 있을 때 메일등을 통해서 프로젝트 메인 개발자에게 통보하는게 보통이다.

익명 CVS를 허용하기 위해서는 우선 시스템에 anonymous계정이 만들어져 있어야 한다. 쉘을 가지지 못하도록 설정한다.
# useradd anonymous -s /bin/false


그리고 /cvsroot/CVSROOT/passwd 파일에 위의 계정을 등록하면 된다.
# echo anonymous: > /cvsroot/CVSROOT/passwd


정확하게는 [유저아이디]:[패스워드]의 형태가 되어야겠지만 익명 CVS의 경우 패스워드를 설정하지 않는게 일반적이므로 패스워드는 생략하도록 한다. 패스워드를 부여하고 싶다면 crypt된 문자열값을 사용하도록 한다.

익명 CVS사용자의 경우 아래와 같이 설정해서 읽기만 가능하도록 권한을 제한시킨다.
# echo anonymous > /cvsroot/CVSROOT/readers





--------------------------------------------------------------------------------

3.1.4절. CVS 사용자 환경설정
cvs 서버에 프로젝트를 등록시키고, 프로젝트를 업데이트하고, 받아오기 위해서 우리는 "cvs" 라는 전용 클라이언트를 사용하게 된다. "cvs" 다음에 여러가지 명령행 옵션을 이용함으로써, 원하는 작업을 하게 된다.

"cvs" 프로그램을 사용하기 위해서 우리는 CVS 서버에 접근해서 지정된 디렉토리(프로젝트가 저장되는 디렉토리 다른말로 "저장소") 가 어디인지 cvs 프로그램에 알려주어야만 한다. 보통 환경변수인 CVSROOT 를 통해서 "cvs" 에게 CVS 서버의 정보를 알려준다. 그러므로 자신의 홈디렉토리의 .bash_profile 파일에 다음과 같은 내용을 추가시켜주어야 한다. CVSROOT=:pserver:yundream@192.168.0.5:/home/cvs


위의 CVSROOT 에는 "pserver" 은 우리가 CVS 서버에 접근하기 위해서 pserver 방식을 사용할것이며, 서버의 IP는 192.168.0.5 접근 아이디는 yundream 그리고 프로젝트가 저장되어 있는 저장소 디렉토리는 /home/cvs 라는 정보를 가지고 있다.

환경변수 CVSROOT 를 사용하지 않고 -d 를 이용하는 방법도 있다. cvs -d :pserver:yundream@192.168.0.5:/home/cvs [옵션]


그러나 이방법은 불편하므로 환경변수를 사용하도록 하자.


--------------------------------------------------------------------------------

3.2절. cvs 클라이언트 사용하기
3.2.1절. 로그인 하기 : login
프로젝트를 아무나 접근해서 사용하게 해서는 당연히 안될 것이다. 그러므로 최초에 아이디와 패스워드를 이용한 인증절차를 거치게 된다.

CVS 서버에 로그인 하기 위해서는 "cvs" 다음에 옵션으로 login 을 주고 실행시키면 된다.
# cvs login
CVS password:


그러면 패스워드를 묻는 프롬프트가 떨어질것이다. 자신의 패스워드를 입력하면 인증이 성공된다. 한번 인증이 성공되면 인증에 사용한 여러가지 정보가 .cvspass 에 저장되고 다음부터는 .cvspass 를 사용해서 자동적으로 인증을 하기 때문에 최초에 한번만 login 을 성공하면 된다.

물론 당연히 CVS 서버인 192.168.0.5 에는 yundream 이란 사용자가 등록되어 있어야 하며 이 사용자는 cvs 그룹에 포함되어 있어야 한다.


--------------------------------------------------------------------------------

3.2.2절. 프로젝트 만들고 등록하기 : import
가장 먼저 해야할일은 진행될 프로젝트를 만들고 등록하는 일이다. 우리가 진행하고자 하는 프로젝트는 hello_world 프로젝트이며, 여기에는 hello.c라는 하나의 파일이 포함되어 있다. 다음은 hello.c의 코드이다.

int main()
{
printf("Hello World\n");
}


우리는 단지 hello.c코드가 있는 디렉토리로 이동해서 다음과 같이 import 시켜주면 된다. # cd hello_world
# cvs -d:pserver:yundream@192.168.0.5:/home/cvs import hello_world project start


이 디렉토리는 프로젝트 임포트를 위해서 새로 준비된 디렉토리여야 한다. 그렇지 않고 잡다한 파일들이 있을경우 이들 파일들까지 몽땅 프로젝트에 등록되어 버린다. 만약 환경변수 CVSROOT가 설정되어 있다면 아래와 같이 간단하게 import를 실행할 수 있을 것이다. # cvs import hello_world project start
N hello_world/hello.c

No conflicts created by this import


2번째 인자인 hello_world는 import할 프로젝트의 이름이며 project, start는 프로젝트의 부가 정보들이다 (별로 신경쓸 필요 없다).

이렇게 하면 cvs서버의 저장소에 hello_world란 디렉토리가 생기고 여기에 hello.c가 올라가게된다.

참고: cvs 저장소에 올라가는 파일 원래 파일이름뒤에 ',v'이 붙어서 저장된다. hello.c라면 hello.c,v라는 이름으로 저장되며 여기에는 hello.c의 원래 내용외에 버젼 관리를 위한 각종 정보가 들어가게 된다.



--------------------------------------------------------------------------------

3.2.3절. 프로젝트 가져오기 : checkout
프로젝트 관리자가 프로젝트를 만들었다면 이제 프로젝트 개발자들이 프로젝트를 받아와서 필요한 작업을 해야할 것이다. 프로젝트에 가져오는 방법은 간단하다. "cvs" 다음에 checkout(혹은 co) 옵션을 사용하면 된다. co 옵션 뒤에는 등록할 프로젝트 이름(hello_world)를 명시해 주면 된다.
# cvs server: Updating hello_world
U hello_world/hello.c


성공적으로 프로젝트가 등록되었다. 이제 ls 해보면 현재 디렉토리에 hello_world 라는 프로젝트이름을 가지는 디렉토리가 생겼음을 알 수 있다. 이와 더불어 hello.c역시 확인 가능할 것이다.


--------------------------------------------------------------------------------

3.2.4절. 프로젝트 수정후 적용(업데이트) : commit
그런데 원래의 hello.c를 보면 printf()함수가 선언되어 있는 stdio.h가 빠져있다. 대부분의 경우 문제가 없지만 컴파일러에 따라서 경고메시지를 보내거나 심한경우 컴파일 실패하는 경우도 있다. 그래서 헤더파일을 추가하기로 했다.

#include

int main(int argc, char **argv)
{
printf("Hello world");
}


헤더파일을 추가 시켰다. 이제 프로젝트 내용을 내가 수정한 내용으로 cvs를 업데이트 시켜야 할것이다. 이럴때는 "commit" 옵션을 이용하면 된다. -m 옵션을 이용하여 변경된 내용에 대한 간단한 로그도 남겨줄수 있다.

# cvs commit -m "stdio.h 헤더파일 인클루드" hello.c
Checking in hello.c;
/home/cvs/hello_world/hello.c,v <-- hello.c
new revision: 1.2; previous revision: 1.1
done
#


물론 cvs 업데이트를 시킬때는 반드시 컴파일이 되는지 정도는 확인하고 올려야 될것이다. 컴파일도 안되는 코드를 올렸다가는 팀원들에게 원망의 소리를 듣게 될것이다.


--------------------------------------------------------------------------------

3.2.5절. 파일 받아오기/로그 보기 : update, log
이제 여러분은 hello_world 프로젝트의 진행 일원이 되었다. 프로젝트 참여 개발자로서 여러분이 컴퓨터 앞에 앉았다면 가장 먼저 해야할일은 간밤에 누군가 프로젝트를 수정하지 않았는지 확인하고 테스트하는 일부터 해야 한다. 최근 프로젝트에 대한 변경사항은 "up"을 이용해서 확인할 수 있다.
[yundream@myhome hello_world]$ cvs up
cvs server: Updating .
P hello.c
[yundream@myhome hello_world]$


hello.c 라는 파일이 수정 돼었음을 알수 있다. hello.c 는 새로이 수정 되었음으로 누가 어떤 이유로 코드를 수정했는지 확인해볼 필요가 있을 것이다. 이럴때는 "log" 명령을 사용한다. "log"명령을 사용하면 해당 파일에 대한 간략한 로그정보를 얻어 올수 있다.
[yundream@myhome hello_world]$ cvs log hello.c
cvs server: Logging .

RCS file: /home/cvs/hello_world/hello.c,v
Working file: hello.c
head: 1.1
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 1; selected revisions: 1
description:
----------------------------
revision 1.1
date: 2002/06/07 01:36:27; author: yundream; state: Exp;
stdio.h 헤더파일 인클루드
=============================================================================


그러면 버젼정보에서 부터, 누가 수정을 했는지, 언제 수정을 했는지와 commit하면서 남긴 로그메시지 등이 표시 됨으로 hello.c 가 어떤식으로 변경되었는지 대략의 정보를 얻어올 수 있다.


--------------------------------------------------------------------------------

3.2.6절. 버젼별 차이 확인 : diff
이렇게 해서 새로운 소스 파일을 가져왔는데, 어느 코드의 어떤 라인이 수정되었는지 확인하고 싶을 때가 생길 것이다. 이러한 버젼별 코드 변경사항의 히스토리가 남게 된다면 나중에 문제가 생겼을 때 이전버젼의 코드를 쉽게 얻어낼수도 있을 것이다.

cvs는 버젼별 코드변경사항을 모두 저장하며 개발자는 이 내용을 이용해서 코드변경사항을 확인할 수 있을 뿐 아니라 문제가 생겼을시 이전 버젼의 코드를 얻어올 수도 있다.

참고: 물론 이러한 작업을 위해서는 약간의 수작업이 필요한데, 공개된 CVS프론트엔드들은 자동으로 이러한 귀찮은 일을 대신 해준다. 아마도 웹기반의 CVS프론트엔드가 가장 좋은 선택이 될 것이다.


버젼별 변경내용은 diff를 통해서 확인할 수 있다.
//hello.cc에 대해서 1.1버젼과 1.2버젼과의 변경내용을 출력하시오.
# cvs diff -r 1.2 -r 1.1 hello.cc
bash-2.04# cvs diff -r 1.2 -r 1.1 hello.c
Index: hello.c
===================================================================
RCS file: /home/cvs/hello_world/hello.c,v
retrieving revision 1.2
retrieving revision 1.1
diff -r1.2 -r1.1
1,2d0
< #include
<




단지 diff만 사용하면 가장최근의 변경사항을 출력한다.

#cvs diff test.cc


--------------------------------------------------------------------------------

3.2.7절. 파일 추가하기 : add
이렇게 해서 프로젝트를 진행하다 보니 README파일을 추가시켜야 할 필요성을 느끼게 되었다. 이처럼 프로젝트를 진행하다 보면 중간중간 여러개의 파일을 추가해야 될건데 이럴경우 add를 이용해서 파일을 추가하면 된다. # cvs add README
cvs server: scheduling file `README' for addition
cvs server: use 'cvs commit' to add this file permanently


이렇게 해서 파일을 추가하긴 했는데 이렇게 했다고해서 바로 파일이 cvs에 등록되지는 않는다. 반드시 commit 를 해서 파일이 프로젝트에 사용될것이라는것을 승인 시켜줘야 한다.

이제 다른 개발자가 cvs up을 하면 README파일이 추가된것을 확인할 수 있게 된다. # cvs up
cvs server: Updating .
U README





--------------------------------------------------------------------------------

3.2.8절. 충돌의 해결
위의 경우는 "팀장"이 파일을 올리고, "팀원"이 파일을 받아서 수정하고 다시 파일을 올리는 과정을 밟고 있다. 그러나 하나의 프로젝트파일에 대해서 2명이 동시에 작업을 하다보면 충돌되는 경우가 생길수 있을것이다.

예를 들어서 "팀장"과 "팀원"이 동시에 같은 라인을 수정하고 있는데, 팀장이 먼저 commit 를 했다고 하자. 잠시 후에 팀원이 commit 하면, 같은 코드라인에 대해서 충돌이 일어나게 될것이다.

그렇지만 걱정할것 없다. cvs 가 알아서 자동적으로 관리해주기 때문이다. 팀원이 자신의 쏘쓰를 commit 하려고 하면 cvs 는 자동적으로 "코드라인에 충돌이 일어났음으로 먼저 update 를 하시오" 라는 메시지를 출력한다. 그래서 update 를 시키면 어느 부분이 충돌을 일으키는지 표시해준다.

다시 프로젝트 쏘쓰인 hello.c 로 돌아가서, 팀장이 다음과 같이 코드를 약간 수정했다고 가정하자.
#include

int main(int argc, char **argv)
{
printf("Hello World!!!!!\n");
}


그런데 그때 "팀원" 도 동일한 코드를 수정했다.
#include

int main(int argc, char **argv)
{
printf("Hello World??\n");
}


이상태에서 팀장이 commit 시키고, 잠시후에 팀원이 commit 시키려고 하면 다음과 같은 에러가 발생한다. (팀장은 아무 이상없이 commit 시킬수 있다)
[yundream@myhome hello_world]$ cvs commit -m hello.c
cvs commit: Examining .
cvs server: Up-to-date check failed for `hello.c'
cvs [server aborted]: correct above errors first!


이문제를 해결하기 위해서 먼저 update 명령을 이용해서 현재 CVS서버에 저장된 프로젝트 내용을 가져오도록 한다. 그러면 다음과 같은 메시지를 받아볼수 있을것이다.
# cvs up
cvs server: Updating .
RCS file: /home/cvs/hello_world/hello.c,v
retrieving revision 1.3
retrieving revision 1.4
Merging differences between 1.3 and 1.4 into hello.c
rcsmerge: warning: conflicts during merge
cvs server: conflicts found in hello.c
C hello.c


기존의 1.3 버젼과 지금 버젼의 프로젝트와 충돌이 생겼음을 알수 있다. 'C' 는 충돌(confilict)이 있음을 나타내는 단어이다. 이제 hello.c 쏘쓰를 보면 다음과 같이 충돌되는 부분을 표시해줄것이다.
#include

int main(int main, char **argv)
{
<<<<<<< hello.c
printf("Hello World!!!!!\n");
=======
printf("Hello World??\n");
>>>>>>> 1.3
}


이럴경우는 팀장과 연락을 취해서(메일, 메신저, 전화로) 충돌되는 코드에 대해서 서로 조율해야 할것이다. 굳이 조율까지 할 필요 없이 그냥 팀장의 코드를 사용하기로 했다면 충돌된 부분을 팀장의 코드로 조정한다음에 commit시키면 된다.


--------------------------------------------------------------------------------

3.2.9절. 필요없는 파일지우기 : delete
쏘쓰파일중에 더이상 필요가 없어진 파일에 대해서는 delete 명령을 이용할수 있다. README 파일이 더이상 필요 없다면 우선 README 파일을 rm을 이용해서 지워주고.. delete한 후 commit시키면 된다.
# rm README
# cvs delete README
cvs server: scheduling `README' for removal
cvs server: use 'cvs commit' to remove this file permanently

# cvs commit -m "리드미 더이상 필요 없어서 삭제했음" README
Removing README;
/home/cvs/hello_world/README,v <-- README
new revision: delete; previous revision: 1.1
done


이제 다른 프로젝트 개발자가 cvs up을 하면 다음과 같이 README가 삭제되었음을 확인할 수 있게 된다.
# cvs up
cvs server: Updating .
cvs server: README is no longer in the repository


이후 ls를 하면 실제 README파일이 지워져있음을 확인 할 수 있을 것이다. 그렇다면 어떤 악의적인 개발자가 중요한 파일을 지워버리면 잘못해서 해당 파일을 영원히 잃어 버리게 되는 사태가 발생하지 않을지 걱정이 될 수도 있을 것이다. 그러나 이 문제는 그리 크게 걱정할 필요가 없다. 해당 파일은 이미 다른 여러 개발자들이 가지고 있을 것이며, 설혹 그렇지 않다 하더라도 cvs 서버에는 파일이 보존이 되어 있기 때문에 언제든지 복구 가능하기 때문이다. 또한 누가 파일을 삭제 했는지 log를 통해서 쉽게 알아 낼 수 도있다.


--------------------------------------------------------------------------------

3.2.10절. 프로젝트를 완료했을때 : release
프로젝트를 완료했다면, release 옵션을 사용해서 프로젝트를 닫을수 있다. 프로젝트를 닫는다고 해서 저장소의 파일을 완전히 지우는 것은 아니다. 단지 저장소의 프로젝트에 어떠한 수정도 할수없는 상태다.


--------------------------------------------------------------------------------

3.3절. 효율적인 프로젝트 관리를 위한 CVS 사용법
CVS는 여러명의 개발자가 참여한다는 가정하게 사용되어 지므로 이를 효율적으로 프로젝트가 진행되도록 하기 위한 몇가지 지켜야할 사항이 있는데 이들에 대해서 알아보도록 하겠다.

아침에 와서 컴퓨터 앞에 앉았다면, 가장먼저 cvs up 을 이용해서 밤사이에 업데이트 된 내용이 있는지 확인을 한다. 그다음 작업에 들어간다. 공동작업을 할때 가장 중요한것은 상대편 작업자가 무슨일을 하고 있는지에 대해 알아야 하는 것이다. 습관적으로 cvs up을 해줘야 한다.

그리고 꽤 중요한 수정이 있었다고 하면 중간중간에 commit하도록 한다. commit할때도 우선 cvs up을 이용해서 수정사항이 있었는지 확인하도록 하고 당연하지만 반드시 컴파일이 되는지 확인한 후 commit 시켜야 한다.

CVS 를 제대로 사용하기 위해서는 CVS 저장소의 구성을 잘해놓아야 한다. 예를 들어 Project_A 란 프로젝트를 시작한다면 Project_A 란 디렉토리를 만들고 그 아래에 프로젝트에 필요한 각종 디렉토리, 즉 작업문서의 저장을 위한 doc 디렉토리, 실제 쏘쓰가 포함될 src 디렉토리, 인크루드 파일이 존재하게 될 src/include 디렉토리 등, 체계적으로 프로젝트를 관리할수 있도록 미리 저장소를 세팅해 놓아야한다. 물론 나중에라도 디렉토리를 추가할수도 있지만, 이왕이면 미리 프로젝트에 대한 계획을 잘 세워두는게 좋을것이다.
Project_A --+-- doc -+-- readme.txt
| |
| +-- todo_list.txt
| |
| +-- schedule.xls
|
+-- src --+-- Makefile
|
+-- main.cc
|
+-- io.cc
|
+-- include --+-- io.h
| |
| +-- common.h
+-- lib --+-- crypt.a


그리고 가능하면 모듈별로 쏘쓰코드를 나누고, 각 모듈별로 분담해서 개발을 하도록 하는게 프로젝트를 쉽게 관리 하는 방법이다. 그러면 개발자는 자신의 모듈만 신경쓰면서도, 전체의 프로젝트의 흐름에 유연하게 대처할수 있게 된다.


--------------------------------------------------------------------------------

4절. CVS 의 다른 활용들
필자는 개인 정보의 관리를 위해서 CVS 를 사용한다. 회사에서 일하다가 좋은 사이트를 찾았다거나, 중요한 메모 상황이 발생했다거나, 좋은 자료를 찾았을경우, 일정관리 까지 모두 CVS 로 저장해두고 집에 있건 회사에 있건 개인 자료를 공유할수 있도록 만들어 놓았다.

모질라의 북마킹 데이타를 CVS 로 연결시켜 놓았기 때문에, 집에 있건 회사에 있건 동일한 북마킹 데이타를 유지할수 있으며, 일정관리(Koraganizer 을 이용한다), 메모데이타(knotes) 역시 공유 가능하도록 해두어서 편하게 사용하고 있다.

이렇게 CVS를 사용함으로써 PDA, email, 노트북(돈이 좀 있어야 한다) 매체를 이용하지 않고도, 간단하게 데이타 동기화를 이루어 낼수 있다.


--------------------------------------------------------------------------------

5절. 결론
이상 CVS의 설치와 간단한 사용방법과 활용방법에 대해서 얘기 해보았다. 여기에 있는 내용은 CVS 의 많은 기능중 꼭 필요한 10% 정도의 내용만을 다루고 있다. CVS 사용에 대한 깊은 내용을 알기 원한다면 cvs 와 함께 배포되는 man 페이지를 활용하거나 CVS 이야기, CVS 사용 등의 문서를 참고하기 바란다.


CVS 이야기 : http://kldp.org/KoreanDoc/html/CVS-KLDP/index.html
CVS 사용 : http://kldp.org/KoreanDoc/html/CVS_Tutorial-KLDP


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Develop" 카테고리의 다른 글
  • A Simple Makefile Tutorial (0)2007/07/19
  • 세마포어의 사용 (0)2007/05/14
  • cvs를 이용한 프로젝트 관리 (2)2007/05/14
  • 공유 라이브러리 이해하기 (0)2007/05/10
  • 컴포넌트를 안전하게 호출하기 (0)2007/05/10
2007/05/14 17:22 2007/05/14 17:22
Posted by webdizen
Tags CVS, 프로젝트 관리
1 Trackback 1 Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2945

  1. cvs서버 설치하기  Delete

    2008/01/09 21:10 Tracked from3인3색

    CVS((Concurrent Versions System, 동시 버전 시스템)는 동시 버전 관리 시스템(Concurrent Versioning System)으로도 알려져 있으며, 버전 관리 시스템을 구현한다. 보통 소프트웨어 프로젝트를 진행할 때, 파일..

Leave your greetings.

  1. 4ipperz

    좋은 정보 감사합니다...cvs에서 익명계정 설정하다가 뜨는 에러를 처리하는데 도움이 됬습니다..(컴퓨터에 cvs그룹은 있고 cvs유저가 없을 줄이야..)

    2008/01/09 21:12 [ Permalink : Modify/Delete : Reply ]
[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/10 09:17

공유 라이브러리 이해하기

공유 라이브러리 이해하기
난이도 : 초급


Peter Seebach
프리랜스 작가
2005년 1 월 11 일

공유 라이브러리는 애플리케이션들이 사용하는 라이브러리에 업그레이드를 적용할 때 버전 숫자를 사용한다. 동시에 오래된 애플리케이션과의 호환성도 유지한다. 이 글에서는 일반적인 리눅스 시스템에 /usr/lib로의 심볼릭 링크가 많은 이유를 설명하겠다.
공유 라이브러리들은 현대적인 유닉스 시스템상에서 공간과 리소스를 효율적으로 사용하기 위한 기본 컴포넌트들이다. SUSE 9.1 시스템 상의 C 라이브러리는 약 1.3 MB를 차지하고 있다. /usr/bin(나의 경우는2,569 이다!)에서 모든 프로그램을 위한 라이브러리는 2 기가 바이트 공간을 차지한다.

물론 이 숫자는 과장된 것이다. 정적으로 링크 된 프로그램들은 자신들이 사용하고 있는 라리브러리의 일부하고만 결합한다. 그럼에도 불구하고, 이 모든 중복된 printf() 카피들과 묶인 공간들은 시스템을 팽창시킬 것이다.

공유 라이브러리를 사용하면 디스크 공간 뿐 아니라 메모리를 절약할 수 있다. 커널은 메모리에 한 카피의 공유 메모리를 보유하면서 이를 다중의 애플리케이션들과 공유한다. 따라서 디스크 상에 한 카피의 printf()를 가지고 있을 뿐만 아니라 메모리에도 한 개를 갖고 있는 것이다. 이것은 퍼포먼스에 뚜렷한 영향력을 갖고 있다.

이 글에서 공유 라이브러리에 사용되는 기본 기술을 검토하고 공유 라이브러리 버저닝(versioning)을 활용하여 과거의 단순한 공유 라이브러리 구현 때문에 겪었던 호환성 악몽을 어떻게 극복하는지 알게 될 것이다. 우선 공유 라이브러리가 어떻게 작동하는지 보자.

공유 라이브러리 작동 방법
개념은 이해하기 쉽다. 당신은 라이브러리를 갖고 있고 그 라이브러리를 공유한다. 하지만 프로그램이 printf()를 호출하려 할 때 실제로 어떤 일이 벌어지겠는가? 실제 작동 방식은 약간 더 복잡하다.

동적 링크 시스템 보다 정적 링크 시스템에서 프로세스가 더 간단하다. 정적 링크 시스템에서, 생성된 코드는 함수에 대한 레퍼런스를 처리한다. 링커는 이 레퍼런스를 함수를 로딩했던 실제 주소로 교체한다. 따라서 결과 바이너리 코드는 올바른 주소를 적재적소에 갖게 되는 것이다. 그런 다음, 코드가 실행되면 관련 주소로 점프(jump)한다. 프로그램의 어떤 지점에서 실제로 레퍼런스 되는 객체들에서만 링크할 수 있기 때문에 관리가 간단하다.

하지만 대부분의 공유 라이브러리들은 동적으로 링크 된다. 이것이 의미하는 바는 크다. 함수가 호출될 때 실제로 어떤 주소에 그 함수가 있게 될지 미리 예견할 수 없다. (BSD/OS에 있는 것 처럼 정적으로 링크 된 공유 라이브러리 스키마가 있었지만 이 글에서는 다루지 않겠다.)

동적 링커(dynamic linker)는 링크 된 각 함수에 대해 상당량의 작업을 수행할 수 있기 때문에 대부분의 링커들은 게으르다. 이들은 함수가 호출될 때 그 작업을 실제로 끝낸다. C 라이브러리에는 천 개 이상의 외부에 보여지는 심볼들과, 거의 삼천 개 이상의 로컬 심볼 들이 있다. 많은 시간을 절약할 수 있다.

작동 마법은 Procedure Linkage Table (PLT)라고 하는 데이터 청크이다. 이것은 프로그램이 호출하는 모든 함수를 나열하고 있는 프로그램의 테이블이다. 프로그램이 시작되면 PLT는 각 함수용 코드를 포함하여 함수를 로딩했던 주소에 대한 런타임 링커를 쿼리한다. 그런 다음 테이블의 모든 엔트리를 채우고 그곳으로 옮겨간다. 각 함수가 호출될 때 PLT의 엔트리는 로딩된 함수로 단순히 직접 점프한다.

하지만 여분의 인다이렉션 레이어를 남겨둔다는 것을 기억해야 한다. 각 함수 호출은 점프를 통해 테이블로 바뀐다.

호환성은 관계만을 위한 것은 아니다!
링크하는 것으로 완료한 라이브러리는 이것이 호출하는 코드와 호환되도록 해야 한다. 정적으로 링크 된 실행파일이 있다면 변경될 것이 없다. 동적 링크라면 보장 못한다.

새로운 버전의 라이브러리가 나왔다면? 특히 새로운 버전이 기존 함수의 호출 순서를 변경했다면?

버전 숫자가 구원자다. (공유 라이브러리는 버전을 갖게 될 것이므로). 프로그램이 라이브러리로 링크 되면 버전 숫자를 갖게 된다. 동적 링커는 매칭(matching) 버전 숫자를 검사할 수 있다. 라이브러리가 변경되면 버전 숫자는 맞지 않을 것이고 프로그램은 새로운 버전의 라이브러리로 링크 되지 않을 것이다.

하지만 동적 링크의 강력한 장점들 중 하나는 버그를 픽스하는데 있다. 라이브러리에서 버그를 픽스하고 그 픽스를 활용하기 위해 많은 프로그램들을 재컴파일 할 필요가 없다면 참 좋은 것이다. 가끔은 새 버전으로 링크해야 할 때도 있다.

불행히도 새로운 버전으로 링크해야 하는 경우도 있고, 어떤 경우는 오래된 버전을 구수해야 한다. 해결책은 있다. 두 종류의 버전 숫자이다:

메이저 넘버(major number)는 라이브러리 버전들 간 잠재적 비호환성을 나타낸다.
마이너 넘버(minor number)는 버그 픽스들만을 나타낸다.
대부분의 경우 같은 메이저 넘버와 더 높은 마이너 넘버를 가진 라이브러리를 로딩하는 것이 안전하다; 높은 메이저 넘버를 가진 라이브러리를 로딩하는 것은 안전하지 못하다.

사용자들(프로그래머들)이 라이브러리 넘버와 업데이트를 트래킹하지 않도록 하려면 시스템은 많은 심볼릭 링크들이 있어야 한다. 일반적일 패턴은 다음과 같다.

libexample.so

는 다음으로 링크 된다.

libexample.so.N

이 시스템에서 N은 가장 높은 메이저 버전 숫자이다.

지원되는 모든 메이저 버전 넘버들은,

libexample.so.N

다음으로 링크 된다.

libexample.so.N.M

M은 가장 큰 마이너 버전 넘버이다.

따라서 -lexample을 링커에 지정하면 최근 버전에 대한 심볼릭 링크인 libexample.so를 찾는다. 한편, 기존 프로그램이 로딩되면 libexample.so.N을 로딩할 것이다. N은 원래 링크 되었던 버전이다.

디버그 하기 위해서는 우선 컴파일 방법을 알아야 한다!
공유 라이브러리로 문제들을 디버깅하기 위해서는 이들이 어떻게 컴파일 되는지를 알아두는 것도 유용하다.

전통적인 정적 라이브러리에서, 생성된 코드는 일반적으로 .a로 끝나는 이름을 가진 라이브러리 파일로 바인딩 되고 링커로 전달된다. 동적 라이브러리에서, 라이브러리 파일의 이름은 일반적으로 .so로 끝난다. 파일 구조는 약간 다르다.

일반적인 정적 라이브러리는 ar 유틸리티에서 만들어진 포맷에 있다. 이는 기본적으로 매우 단순한 아카이브 프로그램으로서 tar 와 비슷하지만 더 단순하다. 반면 공유 라이브러리들은 일반적으로 보다 복잡한 파일 포맷으로 저장된다.

현대적인 리눅스 시스템에서, 이는 일반적으로 ELF 바이너리 포맷((Executable and Linkable Format)을 의미한다. ELF에서, 각 파일은 하나의 ELF 헤더, 0 또는 세그먼트 그리고 0 또는 몇몇 섹션 등으로 구성된다. 세그먼트는 파일의 런타임 실행에 필요한 정보를 포함하고 있고, 섹션에는 링크와 재배치를 위한 중요한 데이터가 포함된다. 전체 파일에서 각 바이트는 한번에 단 한 섹션을 차지한다. 하지만 섹션에 들어가지 않은 고아 바이트가 있을 수 있다. 일반적으로 유닉스 실행파일에서 한 개 이상의 섹션들은 하나의 세그먼트에 둘러 쌓인다.

ELF 포맷은 애플리케이션과 라이브러리를 위한 스팩을 갖고 있다. 이 라이브러리 포맷은 객체 모듈의 아카이브 보다 훨씬 더 복잡하다.

링커는 심볼에 대한 레퍼런스들을 통해 소팅하면서 그들이 어떤 라이브러리들을 발견했는지를 기록한다. 정적 라이브러리에서 온 심볼들은 최종 실행파일에 추가된다; 공유 라이브러리에서 온 심볼들은 PLT에 놓여지고 PLT에 대한 레퍼런스들이 만들어진다. 일단 이 작업들이 수행되면 결과 실행 파일들은 런타임 시 로딩 될 라이브러리에서 검색할 심볼 리스트를 갖게 된다.

런타임 시, 애플리케이션은 동적 링커를 로딩한다. 사실, 동적 링커 자체는 공유 라이브러리들과 같은 종류의 버저닝을 사용한다. 예를 들어, SUSE Linux 9.1에서 /lib/ld-linux.so.2파일은 /lib/ld-linux.so.2.3.3에 대한 심볼릭 링크이다. 반면 /lib/ld-linux.so.1을 찾는 프로그램은 새 버전을 사용하려고 하지 않는다.

동적 링커는 재미있는 작업을 수행해야 한다. 프로그램이 원래 어떤 라이브러리(그리고 어떤 버전)에 링크 되었는지를 찾아서 이들을 로딩한다. 라이브러리 로딩은 다음으로 구성된다:

찾기(시스템 상의 여러 디렉토리들 중 하나에 있을 수 있다)
프로그램의 어드레스 공간으로 매핑하기
라이브러리에 필요한 제로 메모리 블록 할당하기
라이브러리의 심볼 테이블 어태치하기
이 프로세스의 디버깅은 쉬운 일이 아니다. 여러 유형의 문제들을 경험하게 된다. 예를 들어, 동적 링커가 기존 라이브러리를 찾지 못하면 프로그램 로딩에 실패하게 된다. 원하는 모든 라이브러리를 찾았지만 심볼을 찾지 못하면 이 역시 실패한다. (하지만 그 심볼로 레퍼런스를 시도할 때까지 작동하지 않을 수도 있다)—이것은 일반적으로 드문 경우이고, 심볼이 없다면 초기 링크 때 공지된다.

동적 링커 검색 경로 변경하기
프로그램을 링크할 때, 런타임 시 검색할 추가 경로를 지정할 수 있다. gcc에서 문법은 -Wl,-R/path이다. 프로그램이 이미 링크 되었다면 환경 변수 LD_LIBRARY_PATH를 설정하여 이 작동을 변경할 수 있다. 일반적으로 이것은 애플리케이션이 시스템 디폴트의 일부가 아닌 경로를 검색하려고 할 때에만 필요하다. 대부분의 리눅스 시스템에서는 드문 경우이다. 이론상으로는 Mozilla 사용자들은 이 경로 세트로 컴파일 된 바이너리를 배포했지만 실행 파일을 시작하기 전에 라이브러리 경로를 적절히 설정하는 래퍼 스크립트를 배포하는 것을 더 선호한다.

라이브러리 경로 설정은 두 애플리케이션들이 비 호환 버전의 라이브러리를 요구하는 드문 경우에 대안을 제공할 수 있다. 특별한 버전의 라이브러리를 사용하여 디렉토리에서 한 개의 애플리케이션 검색을 갖는데 래퍼 스크립트가 사용될 수 있다. 최상의 솔루션이라고는 볼 수 없지만 어떤 경우에는 이보다 더 나은 것이 없다.

많은 프로그램에 경로를 추가해야 하는 급박한 경우라면 시스템의 디폴트 검색 경로를 변경할 수 있다. 동적 링커는 /etc/ld.so.conf를 통해 제어된다. 여기에는 기본적으로 검색할 디렉토리 리스트가 포함되어 있다. LD_LIBRARY_PATH에서 지정된 모든 경로는 ld.so.conf에 나열된 경로에 앞서 검색된다. 따라서 사용자들은 이들 설정을 오버라이드 할 수 있다.

대부분의 사용자들은 시스템 디폴트 라이브러리 검색 경로를 변경할 이유가 없다; 일반적으로 환경 변수는 검색 경로를 변경하는데 더 알맞다. 툴킷의 라이브러리들과 링크하는 것 또는 새로운 버전의 라이브러리에 대해 프로그램을 테스트 하는 경우가 그 예이다.

ldd 사용하기
공유 라이브러리 문제를 해결하는 한 가지 유용한 툴은 ldd이다. 이 이름은 list dynamic dependencies의 앞 글자를 딴 것이다. 이 프로그램은 주어진 실행 파일 또는 공유 라이브러리를 찾아서 로딩해야 하는 공유 라이브러리가 무엇이고 어떤 버전이 사용되는 지를 파악한다. 아웃풋은 다음과 같다:

Listing 1. /bin/sh


$ ldd /bin/sh
linux-gate.so.1 => (0xffffe000)
libreadline.so.4 => /lib/libreadline.so.4 (0x40036000)
libhistory.so.4 => /lib/libhistory.so.4 (0x40062000)
libncurses.so.5 => /lib/libncurses.so.5 (0x40069000)
libdl.so.2 => /lib/libdl.so.2 (0x400af000)
libc.so.6 => /lib/tls/libc.so.6 (0x400b2000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)



“단순한” 프로그램이 많은 라이브러리들을 사용한다는 사실은 놀랍다. libhistory는 libncurses를 위한 하나의 호출이다. 이를 찾아내려면 또 다른 ldd명령어를 실행한다:

Listing 2. libhistory


$ ldd /lib/libhistory.so.4
linux-gate.so.1 => (0xffffe000)
libncurses.so.5 => /lib/libncurses.so.5 (0x40026000)
libc.so.6 => /lib/tls/libc.so.6 (0x4006b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)



어떤 경우, 애플리케이션은 여분의 라이브러리 경로를 지정해야 한다. 예를 들어, 첫 번째 몇 줄을 Mozilla 바이너리에서 ldd 를 실행하면 다음과 같다:

Listing 3. 검색 경로에 없는 아이템을 위한 ldd 결과


$ ldd /opt/mozilla/lib/mozilla-bin
linux-gate.so.1 => (0xffffe000)
libmozjs.so => not found
libplds4.so => not found
libplc4.so => not found
libnspr4.so => not found
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x40037000)



왜 이들 라이브러리를 찾지 않는가? 왜냐하면 일반적인 라이브러리용 검색 경로에 없기 때문이다. 사실 /opt/mozilla/lib에 있기 때문에 이 디렉토리를 LD_LIBRARY_PATH에 추가하는 것이 한 가지 방법이 된다.

또 다른 옵션은 경로를 .으로 설정하고 이 디렉토리에서 ldd를 실행하는 것이다. 비록 이는 약간 위험하다. 현재 디렉토리를 라이브러리 경로에 둔다는 것은 실행 파일 경로에 두는 것에 버금가는 위험한 일이기 때문이다.

이 경우 이들이 속해있는 디렉토리를 시스템 검색 경로에 추가하는 것은 좋은 생각이 아니다. Mozilla만 이들 라이브러리를 필요로 한다.

Mozilla 링크하기
Mozilla와 관련하여 단지 몇 줄의 라이브러리 밖에 못 봤다고 생각한다면 다음을 보기 바란다. Mozilla를 시작하는데 왜 많은 시간이 걸리는지 알게 될 것이다.

Listing 4. mozilla-bin


linux-gate.so.1 => (0xffffe000)
libmozjs.so => ./libmozjs.so (0x40018000)
libplds4.so => ./libplds4.so (0x40099000)
libplc4.so => ./libplc4.so (0x4009d000)
libnspr4.so => ./libnspr4.so (0x400a2000)
libpthread.so.0 => /lib/tls/libpthread.so.0 (0x400f5000)
libdl.so.2 => /lib/libdl.so.2 (0x40105000)
libgtk-x11-2.0.so.0 => /opt/gnome/lib/libgtk-x11-2.0.so.0 (0x40108000)
libgdk-x11-2.0.so.0 => /opt/gnome/lib/libgdk-x11-2.0.so.0 (0x40358000)
libatk-1.0.so.0 => /opt/gnome/lib/libatk-1.0.so.0 (0x403c5000)
libgdk_pixbuf-2.0.so.0 => /opt/gnome/lib/libgdk_pixbuf-2.0.so.0 (0x403df000)
libpangoxft-1.0.so.0 => /opt/gnome/lib/libpangoxft-1.0.so.0 (0x403f1000)
libpangox-1.0.so.0 => /opt/gnome/lib/libpangox-1.0.so.0 (0x40412000)
libpango-1.0.so.0 => /opt/gnome/lib/libpango-1.0.so.0 (0x4041f000)
libgobject-2.0.so.0 => /opt/gnome/lib/libgobject-2.0.so.0 (0x40451000)
libgmodule-2.0.so.0 => /opt/gnome/lib/libgmodule-2.0.so.0 (0x40487000)
libglib-2.0.so.0 => /opt/gnome/lib/libglib-2.0.so.0 (0x4048b000)
libm.so.6 => /lib/tls/libm.so.6 (0x404f7000)
libstdc++.so.5 => /usr/lib/libstdc++.so.5 (0x40519000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x405d5000)
libc.so.6 => /lib/tls/libc.so.6 (0x405dd000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x406f3000)
libXrandr.so.2 => /usr/X11R6/lib/libXrandr.so.2 (0x407ef000)
libXi.so.6 => /usr/X11R6/lib/libXi.so.6 (0x407f3000)
libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x407fb000)
libXft.so.2 => /usr/X11R6/lib/libXft.so.2 (0x4080a000)
libXrender.so.1 => /usr/X11R6/lib/libXrender.so.1 (0x4081e000)
libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x40826000)
libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x40850000)
libexpat.so.0 => /usr/lib/libexpat.so.0 (0x408b9000)



공유 라이브러리에 대해 더 자세히 배우기
리눅스에서 동적 링크에 대해 더 자세히 알고 싶다면 여러 방법들이 있다. GNU 컴파일러와 링커 툴 체인 문서화는 잘 되어있다. 하지만 핵심적인 부분은 info포맷으로 저장되어 있고 표준 man 페이지에는 언급되지 않았다.

ld.so의 매뉴얼 페이지에는 동적 링커의 작동을 변경하는 변수 리스트와, 과거에 사용되었던 동적 링커의 다양한 버전들에 대한 설명이 되어 있다.

대부분의 리눅스 문서에는 리눅스 시스템상에서는 일반적으로 모든 공유 라이브러리들은 동적으로 링크 되는 것으로 간주하고 있다. 정적으로 링크된 공유 라이브러리를 만드는 작업은 중요한데, 대부분의 사용자들은 여기에서 어떤 것도 얻지 못한다. 이 기능을 지원하는 시스템에서 퍼포먼스 차이는 확연히 드러난다.

프리패키지(pre-packaged) 시스템을 사용하고 있다면 그렇게 많은 공유 라이브러리 버전이 필요하지 않을 것이다. 링크 되었던 라이브러리를 가진 시스템이기 때문이다. 반면 많은 업데이트와 소스 구현을 해야 한다면 많은 버전의 공유 라이브러리가 생긴다.

언제나 강조하지만 직접 해보는 것이 중요하다. 시스템상의 거의 모든 것들은 같은 몇 개의 공유 라이브러리들을 참조하기 때문에 시스템의 핵심 라이브러리들 중 하나를 고장내면 시스템 복구 툴을 가지고 고생을 해야 한다.

http://www-128.ibm.com/developerworks/k ··· ibs.html
"Develop" 카테고리의 다른 글
  • 세마포어의 사용 (0)2007/05/14
  • cvs를 이용한 프로젝트 관리 (2)2007/05/14
  • 공유 라이브러리 이해하기 (0)2007/05/10
  • 컴포넌트를 안전하게 호출하기 (0)2007/05/10
  • GDB를 이용한 Linux 소프트웨어의 디버깅 (0)2007/05/04
2007/05/10 09:17 2007/05/10 09:17
Posted by webdizen
Tags 공유 라이브러리
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2910

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/10 09:14

컴포넌트를 안전하게 호출하기

콜과 리턴을 핸들하는 방법은 어떤 컴포넌트를 호출하는 것 만큼 중요하다!
난이도 : 중급

David A. Wheeler
연구원, Institute for Defense Analyses
2004년 12 월 16 일

일반적으로, 애플리케이션 프로그램들은 OS, 데이터베이스 시스템, 재사용 가능한 라이브러리, (DNS 같은) 인터넷 서비스, 웹 서비스 등의 다른 컴포넌트들을 호출한다. 이 글에서는 이러한 호출을 악용하는 사람들을 방지하는 방법을 설명한다.
PDF 파일을 보면서 하이퍼텍스트 링크를 클릭하는 것은 파일을 호출한 뷰어를 신뢰하는 한 보안 문제는 생길 리가 없다. 하지만 xpdf version 0.90의 사용자들은 이러한 추측이 완전히 잘못된 것이라는 것을 발견했다.

xpdf 사용자들이 하이퍼텍스트 링크를 클릭했을 때, xpdf는 뷰어를 시작하고(디폴트는 Netscape) URL을 뷰어에게 보냈다. 여기까지는 좋았다. 하지만 xpdf 개발자들이 system()호출을 사용하여 뷰어를 시작하기로 결정했다. 이것이 문제였다.

유닉스 시스템과는 달리, system()은 명령행 쉘을 호출하는데 이것은 전송된 텍스트를 인터프리팅한다. 다시 말해서, 침입자가 쉘에서 특별히 인터프리팅 된 가짜 URL을 가지고 PDF 파일을 만들 수 있었음을 의미한다. 그런 다음 침입자가 누군가가 하이퍼텍스트 링크를 클릭한다는 것을 확신할 수 있으면 사람들로 하여금 그들이 선택한 아무 프로그램이나 실행할 수 있도록 한다. 모든 파일들을 지우거나, 읽을 수 있는 모든 것을 외부 메일 드랍으로 이메일을 보낸다.

솔루션: 보안 컴포넌트 호출하기.

컴포넌트를 재사용하는 것에 대해서...
애플리케이션 프로그램들은 일반적으로 독립적(self-contained)이지 않다. 대게 다른 컴포넌트들을 호출한다. 컴포넌트로는 다음과 같은 것들이 있다:

OS
데이터베이스 시스템
재사용 가능한 라이브러리(동적으로 로딩되는 라이브러리 포함)
인터넷 서비스 (DNS)
웹 서비스
오늘날 대부분의 프로그래밍 언어에는 규모가 큰 빌트인 라이브러리가 포함된다. 이중 어떤 것은 독립적이고 어떤 것은 외부 프로그램이나 서비스들을 호출하도록 설계된다.

재사용 문제는 너무 중요하다-모든 애플리케이션을 위해 처음부터 모든 것을 작성한다는 것은 말이 안된다. 각 컴포넌트를 다시 만들기엔 너무 많은 시간이 걸리기 때문이다. 그리고 재사용 할 때 잠재적인 보안 관련 모험을 감수해야 한다-개발자들은 그들의 컴포넌트가 보안 취약성이 없다는 것을 확인하고 보안 컴포넌트의 혜택을 더 많은 시스템들에 전달하는데 집중할 수 있다. 나중에 취약점이 발견되더라도 한번에 픽스되고 이 컴포넌트를 사용하는 모든 애플리케이션에 이 픽스가 적용될 수 있다.

하지만, 다른 컴포넌트를 호출하는 것에도 단점은 있다: 침입자들이 이 호출을 이용할 수도 있다.

Open Web Application Security Project's (OWASP)의 "Top Ten Most Critical Web Application Vulnerabilities" 는 가장 심각한 취약점 중 하나를 투입 오류(injection flaws)로 구분했다. OWASP는 웹 애플리케이션들은 매개변수를 외부 시스템이나 로컬 OS에 전달하고:

침입자가 악의적인 명령행을 이 매개변수에 삽입할 수 있다면 외부 시스템은 애플리케이션 대신 그 명령어를 실행할 것이다.
컴포넌트를 재사용 할 때 면밀히 계획을 세우기 위해서는 다음 사항들을 고려해야 한다:

안전한 컴포넌트만 안전한 방식으로 사용한다.
유효 데이터만 컴포넌트로 보내고 기대하는 대로 인터프리팅 될 것을 확인한다. 특히, 메타-캐릭터(SQL 투입, 쉘 메타-캐릭터 투입, 포맷 스트링, Perl open() 침입 등의 원인)를 주의한다.
리턴 값을 점검하고 예외를 핸들한다.
애플리케이션과 컴포넌트 사이를 거쳐갈 때, 데이터를 보호한다.
이제부터 좀더 자세히 살펴보자.

안전한 컴포넌트, 안전한 방법
작은 함수라 하더라도 무엇인가를 재사용 하기 전 첫 번째 단계는 문서를 검토하여 보안 경고가 있는지를 확인하는 것이다. 예를 들어, C 표준에는 gets()함수가 포함되지만 이에 관한 문서는 gets()가 위험하다고 경고하고 있다. 문제는 gets()가 버퍼 오버플로우에서 자신을 보호할 수 없다는 것이다. 침입자는 gets()에 전달된 버퍼가 저장할 수 있는 것 보다 더 많은 데이터를 보낸다. 이로서 침입자는 내부 데이터를 제어하게 되고 프로그램을 접수하게 된다.

여기 또 다른 예제가 있다: 모든 언어는 실제로는 예견 가능한 "랜덤(random)" 값을 리턴하는 함수를 갖고 있다. 이 함수들은 시뮬레이션에는 이상적이지만 보안용 비밀 키에 사용한다면 매우 위험하다. 왜냐하면 침입자가 그 키를 결정하기 쉽기 때문이다.

광범위하게 사용되는 컴포넌트를 사용하고 있다면 웹 검색을 수행하여 알려진 보안 이슈가 있는지 또는 가볍게 사용할 일반적인 방법이 있는지를 검토해도 좋다.

휴먼 인터랙션을 위한 전체 프로그램을 재사용하려 한다면 특별히 조심해야 한다. 좋은 규칙은 그 일을 하지 않는 것이다. 사용자 인터랙션 프로그램들은 도움이 되고 사용자들의 의도를 생각한다. "도움"은 침입자들이 프로그램을 오용할 데이터를 만드는 데에도 도움이 된다.

휴먼 인터랙티브 프로그램들은 종종 시간이 흐르면서 미묘한 변화들을 겪는데 이것은 사용자들에게는 좋은 것이지만 이 프로그램을 호출하는 프로그램에 혼란을 줄 수 있다. 또한 이 프로그램들에는 사용자들이 다른 프로그램들을 호출할 수 있도록 하는 함수들을 갖고 있다. 예를 들어, vim (vi), emacs, "ed" 프로그램 같은 텍스트 편집 프로그램들에는 사용자가 OS 명령어 쉘로 호출할 수 있는 함수가 포함되어 있어 침입자들이 악용할 수 있는 손쉬운 목표물이 되고 있다. 유닉스 계열 시스템상에서 텍스트 편집을 할 때 sed, tr, gawk, perl 같은 프로그램에서 사용할 명령어들 중 하나를 사용하는 것이 더 안전하다.

워드 프로세싱, 스프레드시트 프로그램의 경우도 마찬가지다. 일반적으로 휴먼인터랙티브 프로그램들은 다른 프로그램에 의해 호출될 수 있는 컴포넌트들에 구현되도록 작성되어야 한다. 이러한 방식으로, 사용자 인터페이스를 픽스하기 더 쉬워지고 다른 프로그램들이 프로그램의 기능들을 재사용하기 더 쉬워진다.

유효 데이터를 전달하여 메타-캐릭터 문제 피하기
컴포넌트가 재사용된다면 이것은 특정 애플리케이션을 위해 만들어지지 않을 것이다. 대신 재사용된 컴포넌트는 많은 기능들을 갖춘 훌륭한 인터페이스를 이용한다. 침입자들은 당신이 기대하지 않던 것을 수행하는 애플리케이션으로 데이터를 보낼 것이다.

어떤 데이터가 당신이 재사용하고 있는 것으로 보내질 수 있는지를 정확히 알고 유효 데이터를 확실히 보내는 것이 중요하다. 의심 할 것이 전혀 없다면 컴포넌트에 데이터를 보내기 전에 검사해야 한다. 숫자가 0에서 100 사이라면 검사하라. 반드시 컴포넌트가 사용하게 될 것과 같은 데이터타입을 사용한다. 컴포넌트가 서명 된 정수를 기대한다면 데이터를 서명 된 정수로 변환한 다음 검사한다. 특히 컴포넌트가 가장 작은 값인 0을 가진 서명 된 정수를 기대한다면 정확히 그 부분에 대해 검사하라. 거의 모든 언어에서, 서명 되지 않은 정수는 서명 된 정수로 바뀔 때 음수로 변경된다. 서명 비트가 일반적으로 서명 된 값에서는 가장 큰 비트이기 때문이다.

가능한 한 여러분이 보내는 데이터가 침입자들에 의해 제어되지 않도록 하라. 빈번한 심각한 포맷 스트링 침입은 침입자가 데이터를 디스플레이하는데 사용된 포맷을 제어할 수 있다는 개념을 바탕으로 한다. 하지만 포맷이 프로그램에서 연속적이라면 침입자가 제어할 것은 없다. gcc 컴파일러 옵션인 -Wformat-security는 코드가 포맷 스트링 침입에 취약하다는 것을 경고한다.

컴포넌트가 여러분이 보내는 데이터를 여러분이 기대하는 방식으로 인터프리팅 하는 것인지를 확인해야 한다. 침입자가 그 데이터의 값에 영향을 줄 수 있을 경우라도 그렇게 해야 한다. 이렇게 하면 매우 일반적인 문제인 메타-캐릭터 취약성(meta-character vulnerability)문제가 발생한다. 많은 복잡한 재사용 컴포넌트들은 고유의 복잡한 명령어 언어를 구현한다. 침입자는 컴포넌트에 의해 인터프리팅 될 텍스트를 여러분이 의도하지 않았던 어떤 것을 수행하기 위해 삽입을 시도할 수 있다. 이는 유닉스 명령어 쉘(/bin/sh) 과 데이터베이스 시스템에 대한 SQL 명령어에서 가장 빈번히 발생한다.

쉘 취약성
유닉스 계열 시스템의 명령어 쉘, 특히 표준 쉘인 /bin/sh는 매우 유용하다. 다른 프로그램들을 한데로 쉽게 결합시켜 유용한 작동을 한다. 쉘에 대한 한 줄의 텍스트 라인이 백 줄의 프로그램과 같은 작동을 할 수 있다는 것은 놀랄 일이 아니다.

표준 쉘은 다른 프로그램들을 통합하기 쉽도록 설계된 많은 빌트인 기능들을 갖춘 완전한 프로그래밍 언어이다. 많은 프로그래밍 언어에는 빌트인 기능들이 포함되어 있어 표준 쉘인 C의 system(3)과 popen(3)을 호출하고 backtick (`) 연산자는 표준 쉘을 호출한다. Windows와 MS-DOS의 사용자들만 유닉스 계열 시스템에서 쉘이 빈번히 사용된다는 데에 놀란다. COMMAND.COM은 매우 제한적이고 같은 기능을 할 수 없기 때문이다.

특히 많은 문자들이 쉘에 대해 특별한 의미를 갖고 있어서 일반 침입 트릭은 이러한 특별한 문자들을 호출 될 경우 쉘로 재전송하기 위해 프로그램을 얻으려 시도하는 것이다.

최상의 솔루션은 프로그램 내부에서 직접 쉘을 호출하지 않는 것이다. 실수하기 쉽기 때문에 단지 그렇게 하지 않으면 된다. 다시 말해서, 보안 프로그램에는 system()과 popen()을 사용하지 않는 것이 좋다. 특히 이러한 호출들로 보내지고 있는 것이 일정한 것이 아닐 때 더욱 그렇다. setuid/setgid 쉘 스크립트를 작성하지 말라; 리눅스 기반 시스템에서는 전혀 작동하지 않으며 다른 유닉스 계열 시스템상에서의 보안 취약성이 있다.

주의를 기울인다면 setuid/setgid가 아닌 쉘 스크립트를 작성할 수 있다. 쉘 스크립트는 다른 쉘의 사용 만큼 나쁘지는 않다. 스크립트 자체는 파일 내에서 정적이기 때문이다. 하지만 이 쉘을 사용해도 단점은 있다. 침입자들이 프로그램을 제어할 수 있게 쉘 안에서 무엇인가를 수행하기 쉽기 때문이다.

쉘 스크립트를 작성하고 있다면 각 프로그램의 전체 경로명을 써서 침입자가 PATH를 조작하더라도 침입하기 어렵게 만든다. 이것을 호출하는 setuid/setgid 프로그램이 있다면 환경을 우선 정리해야 한다. (합당한 PATH 값 설정 포함).

프로그램에서 직접 쉘을 호출할 것이라면 환경이 안전하게 설정되었는지를 확인한다. 침입자들은 문제를 일으키는 PATH, IFS, 기타 환경 변수들을 가지고 장난친다. 전체 경로명으로 명령어를 호출한다.

쉘을 직접 호출할 때 가장 큰 문제는 믿을 수 없는 데이터에서 나온 쉘 데이터를 보내는 경우이다. 침입자가 특별한 의미를 가진 문자를 쉘에 보낼 수 있다면 문제가 생길 것이다. 그와 같은 문자들이 많이 있다.

쉘에서 더블 쿼트 (")는 또 다른 더블 쿼트로 끝난다. 세미 콜론(;)은 명령어를 끝내고 새로운 것을 시작한다. 예를 들어 다음 코드를 가진 C++ 프로그램을 생각해 보자:

Listing 1. 취약한 C++ 코드






침입자가 변수 파일이름의 값을 제어할 수 있는지를 묻고있다. 침입자가 할 수 있다면 문제인 것이다.

이와 같은 코드가 어떻게 이용되는지를 예제를 통해 보자. SLOCCount는 프로그램에서 코드의 소스 라인들의 수를 세는 프로그램으로서 내가 직접 작성한 것이다.(참고자료) 어떤 사람들은 이전 코드와 같아 보이는 SLOCCount에 코드를 추가한 것이라고 생각한다. 하지만 어떤 사람들은 SLOCCount를 사용하여 그들이 작성하지 않은 프로그램들을 측정한다는 것을 깨닫는 것은 중요하다. 사실 그들이 측정하고 있는 프로그램은 침입자가 작성한 것일 수도 있다. SLOCCount가 침입자들에 의해 작성된 프로그램으로 보내져서 SLOCCount 사용자들에게 해를 끼칠 수 있기 때문에 SLOCCount는 침입자로부터 사용자들을 보호해야 한다.

침입자가 그들이 가진 파일들 중 하나를 x ; ;rm -fr ~로 이름을 정했다고 가정해보자. 이 파일 이름들은 가능하다. 다시 말해서 이 시스템 명령어가 쉘에 다음의 명령어를 보낸다는 것을 의미한다:

md5sum < x ; rm -fr ~ > ./my-output

이것은 SLOCCount를 실행하는 사람의 홈 디렉토리 전체를 지울 수 있다. 이 프로그램이 네트워크 인터페이스를 갖고 있지 않더라도 보안에 대해 걱정할 필요가 없다. 침입자들이 일부 인풋을 제공할 수 있기 때문이다.

이 코드에는 다른 잠재적 문제가 있다. 결과를 저장하는 방식 때문이다. my-output에 작성하는 한 개 이상의 프로그램이 있다면? 침입자가 파일, 디렉토리, 조상 디렉토리를 조작할 수 있겠는가?

메타-캐릭터 문제에 대한 간단한 솔루션은 인풋을 메타-캐릭터가 아닌 문자(A-Z, a-z, 0-9)로만 인풋을 제한하는 것이다. 가끔씩 이렇게 할 수 있다. .

그러나, 쉘을 사용해야 하고 들어오는 데이터를 제한할 수 없다면 명령어에 있는 것을 쉘로 보내기 전에 모든 잠재적인 메타-캐릭터들을 취소해야 한다. 쉘의 경우 의심이 가는 모든 문자 앞에 "\" 문자를 삽입하여 명령어의 일부로서 쉘에 보낸다. NUL 문자는 절대 허용하지 말라. 어떤 쉘은 이것을 핸들하지 않는다.

SQL 투입
SQL 투입은 쉘 메타-캐릭터와 같은 문제이다. 다만 쉘 대신 SQL 인터프리터 문제이다.

SQL 투입 침입에서 프로그램은 SQL 명령어를 만들고 이를 SQL 인터프리터로 보낸다. 프로그램은 침입자가 SQL 명령어의 의미를 변경하는 문자를 추가할 수 있도록 한다. 침입의 결과는 광범위하다. 기밀로 유지하고 싶은 데이터를 드러낼 수도 있고 임의의 데이터 변경일 수도 있으며 허용해서는 안될 곳의 인증을 하락할 수도 있고 데이터베이스를 묶어 시스템을 사용할 수 없게 만들 수도 있다.

SQL 투입 침입은 고가의 사이트를 해칠 수 있는 취약성이다. 사이트가 SQL 데이터베이스의 사용을 정렬할 수 있을 만한 데이터를 갖고 있다면 그 데이터는 누군가에 의해 악용될 충분한 소지가 있다. 쉘로의 호출을 재작성 하기 쉬워서 쉘은 개입하지 않아도 SQL 요청을 피할 수 없다. 일반적으로 프로그램의 전체 포인트는 SQL 데이터베이스에 저장된 데이터로 작업한다.

취약한 코드는 무엇을 찾고 있는지 일단 알고 있을 경우 찾기 쉽다. SQL을 호출하는 그릇된 방식은 스트링 연결을 수행하여 극도로 좁은 스크리닝을 경험하지 않은 데이터를 사용하는 SQL 명령어를 만드는 것이다. 다음의 Perl 코드는 그 문제의 예이다: $cmd = "SELECT salary,lastname,firstname FROM employee WHERE eid=" . $eid; 뒤에는 SQL 명령어인 prepare()와 execute() 에 대한 명령어가 뒤따른다. 여기에서는 간단한 스트링 연결을 갖고 있다. ("."는 Perl에서 연결 연산이다.) $eid 변수가 단순한 숫자를 갖고 있다면 기대하는 대로 작동한다. 하지만 침입자가 $eid를 조정하여 "5 OR 1=1" 값을 갖도록 한다면 $result 는 전체 테이블에서 선택된 칼럼을 포함하는 것으로 끝난다.

다른 트릭은 ";"를 삽입하여 다른 명령어를 실행하면서 여분의 더블 쿼트(")를 삽입하여 쿼트 밖의 스트링을 일찍이 취소하는 것이다. 이러한 종류의 침입에는 무한한 변이들이 있다. 기본적으로 간단한 스트링 연결이나 스트링 대체를 사용하여 SQL 쿼리를 만든다면 문제를 버는 것이다.

이러한 잠재적 문제를 확인하는 두 가지 침입 유형이 있다:

받아들이기 전에 아무 데이터나 검사한다.
취약하지 않은 루틴을 사용한다.
데이터를 받아들이기 전에 원하는 포맷을 설명하는 정규식을 정의하고 이 포맷에 맞지 않는 모든 것을 거절한다. 가능하다면 포맷이 SQL에서 통어적 의미를 갖고 있는 문자를 받아들이지 않는지를 확인한다. 문자와 숫자에 대한 값을 제한하라. Legal 리스트에 공백 문자와 구두점을 포함하지 않음으로서 조작의 잠재적 위험성을 방지할 수 있다.

신중하게 핸들하지 않는다면 문제를 일으킬 수 있는 데이터를 받아들여야 한다. 이 같은 것을 염두하고 텍스트를 연결하여 SQL 명령어를 결코 만들지 말라. 대신 라이브러리 루틴들을 검사하여 그와 같은 실수에서 자동으로 보호되는 고급 루틴들을 찾도록 하라.

다른 언어들은 "bound" 또는 "placeholder" 또는 "prepared" 또는 "parameterized" SQL 문장 같은 다른 것들을 호출한다. 예를 들어, PHP는 사용자 데이터를 정확히 대체하는 bind_param 메소드를 갖고 있다. 이 루틴들은 사용자가 제공한 데이터가 기대했던 포맷으로 있는지를 확인해야 한다. (숫자라면 루틴은 어떤 것이든 받아들인다.); 그런 다음 적절한 escaping를 가진 데이터를 삽입하여 SQL 인터프리터에 의해 잘못 번역되지 않도록 한다.

이 레벨의 기능이 여러분이 선택한 언어에서 사용할 수 없다면 루틴을 만들거나 적어도 쿼리를 만들기 위해 다른 스트링과 이들을 결합하기 전에 기대한 포맷과 값이 매치하는지 검사하는 특별한 루틴을 만든다. 루틴을 직접 작성해야 한다면 어떤 데이터든 정확히 쿼트(")를 달도록 한다.

다음은 취약성을 설명하는 일반적인 예제이다.

불행히도 침입자로부터 그 데이터를 인터프리팅하는 라이브러리로 데이터를 옮기는 것이 일반적이다. C에서 일반적인 실수는 침입자 데이터를 포맷 스트링 매개변수(이를 테면 printf(3)의 첫 번째 매개변수)로 전달하는 것이다. printif 포맷 스트링은 데이터를 작성할 수 있고 %n 명령어를 사용함), 임의의 데이터를 드러내면서 이를 심각하게 취약한 것으로 만든다. 다음은 이러한 실수의 예제이다:



포맷 스트링 침입은 일반적으로 프로그램을 목표로 하지만 다른 언어들도 포맷 스트링을 갖고 있다. 이 언어들의 경우 침입자들이 이들을 제어하지 못하도록 해야 한다. 예를 들어, Python은 빌트인 "%" 연산자를 갖고 있는데 이것은 포맷팅을 수행한다. 이를 상수로 만들어서 침입자가 이 포맷을 제어하지 못하도록 한다.

Perl에서 일반적인 실수는 침입자들에게 시스템에 대한 완전한 제어권을 주는 방식으로 open()함수를 사용하는 것이다. Perl의 open()함수는 보안을 요구하기에는 너무 느슨하다. 예를 들어 침입자가 파일 이름에 대해 정렬하여 pipe 심볼로 시작하거나 공간으로 시작하고 끝나도록 한다면 open()이 특정 문자만 특별히 인터프리팅 하기 때문에 문제가 생긴다.

Perl의 빌트인 함수는 시작 공백과 끝 공백을 없앤다. 일반적으로 Perl에서는 open() 대신 sysopen()을 사용하는 것이 더 낫다. (perlopentut과 perlfunc 참조).

명령행 프로그램을 호출할 때 주의하라. 대부분의 유닉스 계열 프로그램들은 대시(-)를 선행시켜 옵션을 나타낸다. Windows 프로그램들은 일반적으로 슬래시(/)를 사용하지만 가끔은 대시를 사용하여 옵션임을 나타낸다. 침입자가 선행하는 대시 또는 슬래시를 명령행 프로그램으로 전달될 어떤 것 앞에 둔다면 옵션처럼 잘못 인터프리팅 된다.

C (C++ 포함)이외의 대부분의 언어들은 삽입된 NUL 문자로 스트링을 쉽게 저장할 수 있다. 반면 C 언어는 NUL 문자를 사용하여 스트링의 끝을 표시한다. 대부분의 C 루틴들은 중간에서 NUL 문자를 가진 스트링을 핸들 할 수 없다. 이러한 차이는 미미한 것 처럼 보이지만 다음을 생각해보자: 많은 라이브러리들(OS 시스템 호출 포함)은 C 규약을 사용한다. 실질적으로 무엇이든 C 규약을 사용할 수 있다. 따라서 삽입된 NUL 문자를 가진 스트링이 C 규약을 사용하여 라이브러리로 전달된다면 스트링은 갑자기 첫 번째 NUL에서 중단된다. 침입자들은 이것 또는 이와 비슷한 트릭을 사용하여 애플리케이션 프로그램이 보는 것과 호출된 루틴이 보는 것이 다르다는 것을 확인한다.

리턴 값과 예외
데이터를 정확히 보내는 것으로는 충분하지 않다. 들어오는 어떤 것이든 정확히 핸들해야 한다.

응답을 정확히 핸들링하는 것은 C에서 보안 코드를 작성할 때 가장 큰 문제들 중 하나이다. (C는 버퍼 오버플로우를 피하기 힘들다.) C는 예외를 처리하는 빌트인 기능이 포함되어 있지 않기 때문에 기본적으로 어떤 함수 리턴이든 무시한다. 에러를 리턴할 수 있는 함수를 호출할 때 마다 요청이 기대한 결과를 만들어 냈는지 신중하게 검사해야 한다. (그렇지 않을 경우 에러를 처리해야 한다.) 침입자가 이를 야기시킬 수 있으므로 이 경우도 대비해야 한다.

예를 들어, read(2)는 요청된 바이트 숫자 이하를 읽을 수 있고 write(2)는 요청된 바이트 숫자 이하를 작성할 수 있다. 어떤 것은 그렇지 않지만 gcc 사용자들은 -Wreturn-type을 (-Wall의 일부), 사용하기 바란다. "(void)" 를 선행시킴으로서 값을 없앤다. 하지만 신중해야 한다.

이것은 우선 고통이 될 수 있다. 어떤 프로그래머들은 printf() 와 다른 많은 함수들이 실제로 값을 리턴한다는 것을 깨닫지 못하고 있다. 보안에 대해 염려한다면 이것을 깨달아야 한다. C에서 정말로 안전한 프로그램은 "정상" 프로세싱을 핸들하는 작은 양의 코드를 가진 에러 핸들링 코드이다.

감사하게도 대부분의 다른 프로그래밍 언어들에는 예외 핸들링이 포함되어 있어서 에러를 처리하기 더 쉽다. 하지만 너무 기뻐하지는 말라. 어떤 호출에서 어떤 예외가 던져졌으며 적절히 처리하는 방법을 알아야 한다. 특히 반드시 잠금을 해제하고 침입자들이 보낸 데이터로 인한 전채 애플리케이션의 충돌을 피해야 한다.

침입자가 리턴 된 데이터에 영향을 끼칠 수 있다면 조심하라. 예를 들어, DNS resolver를 호출하여 정보를 얻는다면 이 데이터는 침입자가 직접 제공한 데이터임을 기억하라. 이를 조심스럽게 다루고 포맷 또는 크기도 가정하지 말고 믿을 수 있을 때까지 믿어서도 안된다.

인풋인 것처럼 보이는 임의의 데이터를 핸들 할 때 조심하라. (진짜 인풋일수도 있다.) NUL 문자, 무효 문자, 문제를 일으킬 것들을 포함했을 수도 있다.

애플리케이션과 컴포넌트 간 데이터 보호하기
침입자가 애플리케이션과 호출하고 있는 컴포넌트 간 흐르는 데이터와 인터페이싱 또는 읽을 수 없도록 한다. 기저의 OS에 시스템 호출할 할 때 이는 문제가 되지 않는다. (적어도 직접적으로는...)

특히 setuid/setgid 프로그램을 실행한다면 사용자들이 라이브러리 호출을 리다이렉트 하도록 하는 방식은 불가능하고 앞서 언급했듯이, setuid/setgid 프로그램은 환경변수들을 지워 호출하는 것이 무엇이든 보호 받도록 해야 한다.

그러나 그러한 방식으로는 인터넷이라는 광야를 감당할 수 없다. 웹 서비스로의 call을 호출한다면 웹 서비스를 신뢰한다 하더라도 침입자가 애플리케이션과 사용하고 있는 서비스 간 통신과 인터페이싱 할 수 없도록 해야 한다.

기밀성과 무결성을 유지하는 전형적인 솔루션은 이미 존재하고 있는 보안 프로토콜과 암호 알고리즘을 사용하는 것이다. 프로토콜과 알고리즘을 다시 만들지 말라. 일반적인 보안 프로토콜에는 TLS/SSL, OpenSSH, IPSec 등이 있다. RSA, SHA-1, AES, Triple-DES 같은 암호 알고리즘을 적용할 것이다.

암호화하고 인증하는 서비스로의 보안 연결을 만드는 것 만으로도 서비스로의 연에 기밀성과 무결성을 확보할 수 있다. 첫 번째 단계는 이 프로토콜과 알고리즘들이 필요하다는 것을 인식하는 것이다.

이제는 안전하다!
불안전한 방식으로 다른 컴포넌트에 의존한다면 안전한 프로그램을 가질 수 없다. 이 글에서 침입의 일반적인 종류들을 언급했다. 다른 컴포넌트들을 안전하게 호출하더라도 아웃풋을 다시 보내는 방식이 모든 일을 그르칠 수 있다.

침입자들은 프로그램 아웃풋을 이용할 줄 안다. 놀라운 일이 아니다. 다음 글에서는 그와 같은 종류의 침임을 어떻게 다루는지를 설명하겠다.





출처 :http://www-128.ibm.com/developerworks/k ··· lls.html
"Develop" 카테고리의 다른 글
  • cvs를 이용한 프로젝트 관리 (2)2007/05/14
  • 공유 라이브러리 이해하기 (0)2007/05/10
  • 컴포넌트를 안전하게 호출하기 (0)2007/05/10
  • GDB를 이용한 Linux 소프트웨어의 디버깅 (0)2007/05/04
  • 객체 비지향(object disoriented)을 위한 공유 객체 (0)2007/05/04
2007/05/10 09:14 2007/05/10 09:14
Posted by webdizen
Tags 컴포넌트
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2909

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/04 15:00

GDB를 이용한 Linux 소프트웨어의 디버깅

Core was generated by `./eg1'.
Program terminated with signal 8, Floating point exception.
...
#0  0x80483ea in wib (no1=8, no2=8) at eg1.c:7
7         result = no1 / diff;



이 지점에서 'info locals', 'pront', 'info args' 및 'list'를 시행하여 제로로 나눈 값을 알 수 있다. 'info variables' command는 모든 프로그램의 변수 값을 프린트한다. 그러나 gdb가 program code에서는 물론 C library에서도 변수를 인쇄하므로 시간이 오래 걸린다. Wib()을 호출한 함수에서 무슨 일이 발생했는지 알기 위해 gdb의 stack commands를 사용할 수 있다.

스택 트레이스(Stack traces)
"call stack" 프로그램은 현재까지의 함수에 대한 리스트이다. 각 함수와 변수는 프레임 0("bottom" frame)에서 최근에 호출된 함수와 함께 "frame"으로 할당된다. 스택을 프린트하기 위해 'bt'(backtrace) command를 실행한다 :

(gdb) bt
#0  0x80483ea in wib (no1=8, no2=8) at eg1.c:7
#1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21



이것은 wib() 함수가 21 행의 main()에서 ('list21'로 쉽게 확인가능) 호출되었음을 나타낸다. 또 wib()은 frame 0에, main()은 frame 1에 있음을 나타낸다. 이것은wib()이 frame 0 에 있기 때문인데, frame 0는 arithmetic error가 발생할 때 프로그램 내에서 실행되는 함수이다.

'info locals' command를 시행하면 실제 gdb는 국소 변수(variables local)를 현재 프레임에 프린트한다. 이 프레임은 인터럽트 된 함수가 있는 곳에 (frame 4) 디폴트로 존재한다. 현재 프레임은 'frame' 명령으로 프린트 할 수 있다. 메인 함수(frame 1에 있는)의 변수를 알기 위해 'frame 1'후 'info locals'를 실행하면 frame 1로 스위치 할 수 있다 :

(gdb) frame 1
#1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
21          result = wib(value, div);
(gdb) info locals
value = 8
div = 8
result = 4
i = 2
total = 6



이것은 "value"와 div"이 같을 때 "for" 루프 (i와 2는 같다)를 통해 오류가 세 번째 발생했음을 가리킨다.

프레임은 두 가지 방식으로 스위치 가능하다. 프레임의 숫자를 분명하게 'frame' 명령에 지정하거나 또는 스택을 위로 움직일 때는 'up', 아래로 움직일 때는 'down' command 실행한다. 프레임에 대해서 주소나 프로그램 언어 같은 자세한 정보를 원하면 'info frame' command를 실행한다.

Gdb stack commands는 core file과 프로그램 실행에 유효하다. 따라서 복잡한 프로그램의 경우에도 실행 중일 때 함수에 어떻게 도달하는 지 추적할 수 있다.

다른 프로세스에 첨부
Core file이나 프로그램의 디버깅 이외에도 gdb는 이미 실행중인 프로세스에 첨부될 수 있다. Core file name 대신, gdb를 첨부하고자 하는 프로그램의 프로세스 ID를 지정하면 된다. Loop 및 sleep를 실행한 예제 프로그램은 다음과 같다 :

eg2 example code #include
int main(int argc, char *argv[])
{
int i;
for(i = 0; i < 60; i++)
{
   sleep(1);
}

return 0;
}




이것을 'gcc-c eg2.c-0 eg2'로 컴파일 해서 './eg2&'로 실행한다. 실행을 시작할 때 프린트 되는 process ID가 1283이다 :

./eg2 &
[3] 1283



Gdb 시작해서 pid를 지정하면, 'gdb eg2 1283'이 된다. Gdb는 "1283"의 core file을 찾지 못하면 gdb는 process 1283에 첨부된다. Gdb가 어디에서 실행되더라도 관계 없다. (이 경우는 아마 sleep()에 위치한다) :

...
/home/seager/gdb/1283: No such file or directory.
Attaching to program: /home/seager/gdb/eg2, Pid 1283
...
0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
(gdb)



이 지점에서 유용한 gdb가 모두 발생할 수 있다. 'backtrace'를 사용해서 main()과 관련된 위치와 main()의 프레임 숫자가 무엇인지 알 수 있다. 그리고 거기에서 프레임을 스위치 하여 "for" 루프를 몇 번 통과 했는지 알 수 있다 :

(gdb) backtrace
#0  0x400a87f1 in __libc_nanosleep () from /lib/libc.so.6
#1  0x400a877d in __sleep (seconds=1) at ../sysdeps/unix/sysv/linux/sleep.c:78
#2  0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
(gdb) frame 2
#2  0x80483ef in main (argc=1, argv=0xbffff9c4) at eg2.c:7
7           sleep(1);
(gdb) print i
$1 = 50



프로그램 설치가 완료되면 'detach' command로 프로그램을 실행할 수도 있고 'kill' command로 없앨(kill) 수도 있다. 또 pid 1283 아래 eg2를 첨부할 수도 있다. 이는 먼저 'file eg2'를 이용해서 그 안에 파일을 로드한 후 'attach 1283' 첨부 명령을 내림으로서 가능하다.

기타 사항
gdb를 이용하면 'detach' command로 디버깅 환경을 종료하지 않고 쉘 명령을 실행할 수 있다. 디버깅을 하면서 소스코드를 변경할 때 유용하게 쓰이는 'shell [commandline]'로서 호출되기 때문이다.

마지막으로 프로그램 실행 중에도 'set' command로 변수 값을 수정할 수 있다. Gdb에서 eg1을 실행한다. 그리고 'break 7 if diff==0' 명령으로 일부만 언급하였다. 자세한 사항은 GNU Debugger 매뉴얼을 참고하기 바란다.
"Develop" 카테고리의 다른 글
  • 공유 라이브러리 이해하기 (0)2007/05/10
  • 컴포넌트를 안전하게 호출하기 (0)2007/05/10
  • GDB를 이용한 Linux 소프트웨어의 디버깅 (0)2007/05/04
  • 객체 비지향(object disoriented)을 위한 공유 객체 (0)2007/05/04
  • Linux 애플리케이션을 위한 DLL 작성하기 (0)2007/05/04
2007/05/04 15:00 2007/05/04 15:00
Posted by webdizen
Tags GDB, 디버깅
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2897

Leave your greetings.

[로그인][오픈아이디란?]

Unix & Linux/Develop2007/05/04 14:58

객체 비지향(object disoriented)을 위한 공유 객체

동적 로드 라이브러리 작성 방법

Ashish Bansal
소프트웨어 엔지니어 (Sapient Corporation)
2001년 4월

동적으로 로드가 가능한 라이브러리를 작성하는 방법과 프로세스에 사용할 수 있는 툴을 설명한다. 컴파일 과정, 네이밍 규정과 공유 라이브러리의 작성, 컴파일, 설치 방법 등이 소개되어 있다.
사실, 공유 객체(shared object)는 객체 지향 기술과 관련이 없다. 이 글에서는 Linux 플랫폼상의 동적 링크 라이브러리에 대해 다룰 것이다. (Windows의 DLL과 비교해 볼 것). 여러분도 C 에서 printf ()와 같은 간단한 함수에 라이브러리를 사용하거나 C++ 일반 함수 라이브러리에서 sort()와 같은 복잡한 함수에도 라이브러리를 사용해 본적이 있을 것이다. 라이브러리를 이용하면 프로그래밍이 쉬워지며 개발자들이 작업에 집중하는데 도움이 된다.

소프트웨어를 구축할 때 라이브러리에 대한 의존도는 상당히 높다. 라이브러리를 이용 함으로서 소프트웨어는 다양한 일을 처리할 수 있다. 스크린 상에서 프린트가 가능하고 네트워크에 로그도 할 수 있다. 어떤 라이브러리는 시스템에서 제공을 받는다. 또, 어떤 것은 서드파티 벤더가 작성하기도 하고 사용자 스스로 작성하기도 한다. 컴파일이 실행되는 동안 라이브러리는 애플리케이션으로 링크 된다. 애플리케이션이 많은 라이브러리를 사용하고 모든 코드가 링크 된다면 애플리케이션의 사이즈도 커지게 된다. 공유 라이브러리는 애플리케이션 소스로 링크 되지 않았지만 애플리케이션에 의해서 요청된 순간에 동적으로 로드 된다.

컴파일 절차 (Compilation process)
공유 객체를 본격적으로 구축하기에 앞서 컴파일 절차와 공유 객체에 대해서 알아보자. hello world code의 예제를 이용하여 설명하겠다.

Listing 1: Hello.c; 공유 객체 정의



다음과 같은 명령 행을 사용하여 위 프로그램을 컴파일 해보자:




이로써 hello라는 이름의 실행파일이 만들어진다. 다음은 컴파일 절차이다:

구문 검사: 파일의 구문과 문법을 검사한다.
컴파일(compilation): 파일을 컴파일하여 코드에 맞는 목적 파일을 만든다. 이런 경우 printf ()와 같은 미정(unresolved)의 함수 이름은 앞서 만들어진 목적 파일에서 정해진다. (파일 포맷 참조)
3. 링크: 유닉스의 ld와 비슷한 링커(linker) 라는 개별 프로그램을 불러온다. 링커(linker)는 다양한 라이브러리에서 코드를 찾아가면서 함수와 변수를 해독한다. 예를 들어, printf() 에 맞는 코드는 libc.a (또는 libc.so) 파일에 있다. 표준 세트와는 다른 라이브러리가 필요하다면 라이브러리가 지정되어야 한다.
코드의 컴파일과 링크에 대한 위 명령은 아래와 같이 두 가지로 나뉘어질 수 있다:



이것이 컴파일 과정이다.(-c 옵션이 컴파일을 지정한다.) 두 번째 단계는 링크이다. 실행파일 hello를 만들기 위해 ld 프로그램이 사용된다.

파일 포맷
ELF는 Executable and Linking Format의 이니셜이다. 이 포맷은 Linux를 비롯해서 Unix 플랫폼의 객체 파일에 사용된다. 기본적으로 세가지 타입의 파일이 있다:

relocatable 파일은 실행 파일이나 공유객체 파일, 또는 다른 relocatable 파일을 만들기 위해 다른 객체 필드와 링크 될 수 있는 코드와 데이터를 가지고 있다.
실행파일 에는 실행할 준비가 되어있는 프로그램이 있다.
공유 객체 파일에는 다른 공유 객체나 relocatable 파일로 링크될 수 있는 코드와 데이터가 있다. (객체 파일 포맷은 아래 그림을 참조)
모든 파일에는 ELF 헤더가 있다. 이 ELF 헤더는 파일의 처음부분에 있고 나머지 파일들에게 로드맵 역할을 한다. 섹션은 최소의 단위를 나타내며 파일에서 실행될 수 있고 링크에 필요한 정보가 있다. 섹션 헤더 테이블에는 파일의 섹션에 대한 정보를 포함하고 있다. 프로그램 헤더 테이블이 존재한다면 프로세스 이미지를 만드는 방법이 나온다. 프로세스 이미지는 객체파일이 실행파일이 될 때 사용된다. exec 프로그램은 프로그램 헤더 테이블을 사용하여 프로세스를 복제(fork) 한다. ELF 헤더의 위치가 파일에 항상 존재하는지 주목해야 한다. 다른 부분들이 다른 장소에 나타날 수 있다.

ELF 헤더는 file 프로그램에 의해 사용되고 파일에 대한 정보를 출력한다. (본 글의 유틸리티 프로그램과 툴 참조). libelf 라이브러리 패키지는 ELF 헤더의 정보에 액세스할 수 있는 프로그래밍 인터페이스를 제공한다.


이러한 타입의 링크를 정적 링크 (static link)라고 한다. 라이브러리의 코드는 정적 컴파일을 사용해가면서 애플리케이션과 결합한다. 훌륭한 애플리케이션에는 수백개의 함수들이 라이브러리로서 사용된다. 표준 라이브러리, 제 3의 라이브러리, 내부 라이브러리 등이 있다. 정적 컴파일을 사용하면, 최종 실행파일 사이즈는 매우 커지게 되며 모두가 런타임 동안에 메모리로 로드 된다. 그래서 함수가 사용되든 안되든 상관없이 코드는 메모리에 있게 된다.

필요할 때마다 라이브러리가 메모리에 동적으로 로드 되고, 프로그램의 메모리 사용흔적(footprint)을 줄이고, 애플리케이션을 좀 더 작은 부분으로 나눌 수 있는 메커니즘이 있다면 꽤 유용할 것이다. 배포가 쉬워지고, 설치와 업그레이드 역시 가능하게 될 것이다. 그와 같은 메커니즘은 주로 동적 링크 라이브러리(Windows의 DLL, Linux의 Shared Object)에 존재한다. 이 라이브러리를 사용하는 애플리케이션을 동적 실행파일 이라고 한다.


네이밍 규정

공유 객체를 시작하기 전에 라이브러리의 네이밍 규정에 대해 살펴보자. 정적 라이브러리는 일반적으로 lib 라는 문자로 시작되며. .a 확장자를 가지고 있다. 공유 객체는 두 가지의 다른 이름(soname 과 real name) 이 있다. soname은 접두어 "lib", 라이브러리 이름, ".so" , 그리고 주요 버전 넘버로 구성된다. 경로 정보에 접두사를 붙이면 공식적인 soname이 된다. real name은 컴파일 된 라이브러리 코드가 포함되어 있는 실제 파일 이름이다. real name은 "soname"에 마침표(dot)를 붙이고 그 다음에 마이너 버전 넘버, 또 하나의 마침표, 그 다음에 릴리즈 넘버를 더한 것이다. (릴리즈 넘버는 옵션이다).

Program-Library How-To 에서 정의된 다른 이름인 linker name 이 있다. 이것은 버전 넘버가 없는 soname이라고 할 수 있다. 일반적으로 soname에 링크 된다. 그리고 soname은 real name 에 링크 된다.

예를 들어서 soname, /usr/lib/libhello.so.1 를 살펴보자. 이것은 공식적인 soname 이며 /usr/lib/libhello.so.1.5를 가리키는 링크일 수 있다. 상응하는(corresponding) 링커 이름으로 /usr/lib/libhello.so 가 있을 것이다. 관리해야 할 파일이 많아보인다. 하지만 관리를 도와줄 툴이 있다.(유틸리티 프로그램과 툴의 ldconfig 참조)

이제 본격적으로 공유 객체의 샘플을 작성해 보자. 라이브러리의 soname은 libprint.so.1 이고 실제 이름은 libprint.so.1.0이다. 이로서 라이브러리는 printsring (char*)이라는 한 개의 함수를 가지게 된다. 이 함수는 "String:" 뒤에 이 함수에 아규먼트로 전해진 문자를 출력한다.


공유 라이브러리 작성하기

유용한 라이브러리를 만들기 위해 두 가지의 파일은 기본적으로 작성되어야 한다. 하나는 header 파일 이다. 이 header 파일은 라이브러리에 의해 export 되고 클라이언트의 코드에 include 될 모든 함수를 정의한다. 다른 하나는 공유 라이브러리로서 컴파일 되고 위치가 지정되어야 하는 함수의 정의 파일 이다. 우리의 예제에서 header 파일은 다음과 같은 형태를 가진다.


Listing 2: Libprint.h Code; Header file



라이브러리의 코드는 아주 기본적인 것이다. 다음 listing를 보자.

Listing 3: libprint.c Code


_init(void)과 _fini(void)라는 두개의 특별한 함수가 있다. 이 함수들은 라이브러리가 로드 될 때마다 동적 로더에 의해 자동적으로 호출된다. 함수가 호출될 때마다 나오는 진단 메시지를 출력하도록 하기 위해 두 함수를 libprint.c 코드에 추가시켜보자. 이 두 함수는 디폴트로 제공이 되며 이것을 무시하고 자신의 함수를 작성하는 것도 가능하다.

Listing 4: Code for _init() and _fini()



이제 이것을 Listing 3에 있는 코드에 붙여보자. 매우 간단하다. 자신의 라이브러리를 작성하려면 libprint.c 와 libprint.h의 템플릿을 사용한다. 그리고 나서 적절한 함수를 작성한다. 이제 라이브러리를 컴파일 하자.

공유 라이브러리 컴파일
다음은 라이브러리를 컴파일하는 명령 순서 이다:


$ gcc -fPIC -c libprint.c
$ ld -shared -soname libprint.so.1 -o libprint.so.1.0 -lc libprint.o



gcc 명령행에서 -fPIC 옵션을 주목하자. 이것은 Position-independent Code(위치 독립 코드)를 만들어내는데 있어서 매우 필수적인 옵션이다. 이 명령은 "한 프로세스가 활동할 수 있는 공간 어디서든지 로드 될 수 있는 코드를 만들어내는 것" 이라는 의미가 된다. -fPIC 옵션은 공유 객체에 있어서도 매우 중요하다. 이 옵션을 사용하면 수행되어야 하는 relocation의 넘버는 최소가 된다. 실행파일에 의해 사용되는 공유 객체를 로딩할 때 이 옵션을 위해서 어느 정도의 스페이스가 할당되어야 한다. 텍스트와 데이터 섹션이 어떤 위치에 할당되어야 한다. location이 위치 독립 방식(position-independent way)에 의해서 구현되지 않는다면 많은 relocation이 프로그램에 공유 객체를 로딩하는 프로그램에 의해 수행되고 이는 성능에 불리한 영향을 미친다.

이제 ld로 전달 된 옵션을 살펴보자. -shared 옵션은 output 파일이 공유 라이브러리가 될 것이라는 것을 나타낸다. -soname name 옵션을 지정해서 soname으로 어떤 것이 될 것인지를 정한다. -o name은 공유 객체의 real name을 지정한다. soname과 real name은 라이브러리를 설치하는 동안 사용되기 때문에 이 두개를 지정하는 것은 중요하다.

공유 라이브러리의 설치와 사용
지금까지 라이브러리를 구축해 보았다. 라이브러리를 설치해서 라이브러리를 사용하는 작은 클라이언트 프로그램을 만들어보자. ldconfig 라고 불리는 특별한 프로그램은 공유 라이브러리를 설치하는데 사용된다. 일반적으로 공유 라이브러리는 /usr/lib 이나 lib 또는 /usr/local/lib 에서 설치 될 수 있다. 라이브러리가 만들어지면 그러한 디렉터리 중 하나에 복사되어야 한다. 이제 ldconfig 프로그램을 실행시켜보자.

ldconfig
$ldconfig -v -n .
...:
libprint.so.1 => ./libprint.so.1.0





libprint.so.1 에서 libprint.so.1.0 라는 이름의 심볼릭 링크를 만들었다. 다음의 설치 단계는 링커 이름을 위해 다른 링크를 만드는 것이다:

linker name에 링크하기
$ ln -sf libprint.so libprint.so.1





/usr/lib 이나 lib 또는 /usr/local/lib 디렉터리에서 내용을 복사하려면 super user 권한이 필요하다. 애플리케이션이 실행될 때, 이러한 디렉터리는 애플리케이션이 실행될 때 라이브러리를 찾기 위해 자동적으로 검색된다. super user 권한이 없으면 공유 라이브러리는 어떤 디렉터리에도 설치될 수 있다. 하지만 이러한 공유 라이브러리를 사용하는 실행파일이 실행되기 전에 다른 세팅이 이루어져야 한다. 환경 변수 (environment variable), LD_LIBRARY_PATH는 공유 라이브러리가 위치하고 있는 경로를 지정해야 한다. 공유 라이브러리가 실행파일 로서 같은 디렉터리에 있다면 다음과 같이 해보자:

LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH





이제는 구현된 공유 라이브러리를 사용 할 클라이언트를 컴파일 하고 실행시켜 보자. 공유 라이브러리의 사용은 매우 간단하다. :)

Listing 5: Client.c; 라이브러리에서 printstring() 함수를 사용하는 샘플 클라이언트



이 프로그램은 명령 행을 사용하여 실행파일로 컴파일 될 수 있다:


$ gcc -o client client.c -L. -lprint



이것은 client 라는 실행파일 을 만들어내며 라이브러리가 코드와 같은 디렉터리에 있다는 것을 알 수 있다. 이것은 -L. -lprint 를 보고 알 수 있다. 실행을 하면 다음과 같은 output이 생성된다:


$ client
Inside _init()
In Main!
String: In Main!
Inside _fini()
$




지금까지 공유 객체를 만들고 설치하여 사용해 보았다. 이제, client를 실행시키려고 할 때 어떤 일이 일어나는지 살펴보자. 프로그램이 시작되면 시스템은 이 프로그램이 동적 라이브러리에 의존한다는 것을 인식한다. 그래서 시스템은 /lib/ld-linux.so.X 라는 로더를 호출하여 요청 받은 라이브러리를 로드한다. 실행파일이 어떤 라이브러리에 속하는지를 결정하는 데에 ldd를 사용할 수 있다.

ldd
$ ldd client
libprint.so.1 => ./libprint.so.1
libc.so.6 => /lib/libc.so.6
/lib/ld-linux.so.2=> /lib/ld-linux.so.2





이로써, 파일 클라이언트가 어떤 라이브러리에 의존하는지 알 수 있다. 이러한 라이브러리는 로더에 의해 로드된다. 그리고 상응하는 _init () 섹션이 호출될 것이다. 로더는 우선 환경 변수, LD_LIBRARY_PATH 에 나타난 경로에서 라이브러리를 찾는다. 그리고 나서 /etc/ld.so.conf 에 나타난 표준 경로로 간다. 라이브러리를 찾을 수 없을 때 에러가 발생한다. 정상적 환경에서라면 로더는 라이브러리를 로드하여 정상적으로 프로그램을 실행한다.

유틸리티 프로그램 및 툴
이제 매우 유용한 바이너리 유틸리티인, file, nm, objdump 를 살펴보자.

파일 프로그램은 파일 타입을 알아내는 데에 사용된다. 테스트가 된 파일은 텍스트 파일(ex. libprint.c) 이나 실행파일 (ex. client) 또는 데이터 (ex. /dev/hda5) 가 될 수 있다. 특정파일이 어떤 플랫폼을 위해 컴파일 되었는지, 그리고 무엇보다도 이것이 실행파일 인지 아닌지를 알아내는 데에 있어서 파일은 매우 유용하게 쓰인다. 이것은 다음과 같은 명령 행으로 호출된다:

File
$ file client
client: ELF 32-bit LSB executable, Intel 80386, version 1, dynamically linked
(uses shared libs), not stripped




nm 은 객체에 존재하는 모든 심볼을 심볼을 나열한다. (여기서 말하는 객체는 우리가 일반적으로 말하는 객체 파일이나 라이브러리를 의미한다). nm 뒤에 객체 이름을 주고 nm 이라는 프로그램을 실행시키면 nm이라는 프로그램은 이 객체에 의해 사용되거나 익스포트되는 모든 함수, 심볼, 객체 타입을 리스트한다. 심볼을 예로 들면 정의되지 않았거나, 외부 심볼이 될 수 있으며 global이 되거나 다른 식별자가 될 수 있다. libprint.so대해 nm을 실행시키면 다음과 같은 output이 생긴다:

nm
$ nm libprint.so
00001490 A _DYNAMIC
00001480 A _GLOBAL_OFFSET_TABLE
00001510 A __bss_start
00001510 A _edata
00001510 A _end
00000452 A _etext
00000400 T _fini
000003d8 T _init
000003d8 t gcc2_compiled
U printf@@GLIBC_2.0
00000428 T printstring



objdump 는 목적 파일에 대한 정보를 디스플레이 한다. 이러한 객체 파일에 대한 정보는 명령 행에서 지정될 수 있다. objdump로 전해진 옵션은 어떠한 정보를 디스플레이 해야 할 지를 제어한다. 목적 파일 내부를 자세하게 볼 수 있는 유용한 유틸리티 이다.


원문 : http://www-903.ibm.com/developerworks/k ··· obj.html
"Develop" 카테고리의 다른 글
  • 컴포넌트를 안전하게 호출하기 (0)2007/05/10
  • GDB를 이용한 Linux 소프트웨어의 디버깅 (0)2007/05/04
  • 객체 비지향(object disoriented)을 위한 공유 객체 (0)2007/05/04
  • Linux 애플리케이션을 위한 DLL 작성하기 (0)2007/05/04
  • 리눅스에 네트워크 라우터 구현하기 (0)2007/05/04
2007/05/04 14:58 2007/05/04 14:58
Posted by webdizen
Tags 객체 비지향, 공유 객체
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2896

Leave your greetings.

[로그인][오픈아이디란?]

«Prev  1 2 3  Next»

RSS HanRSS
Blog Image
webdizen
이 곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (2998)
Webdizen (134)
Life (6)
Diary (16)
Blog (9)
IDEA (1)
Travel (10)
Book (14)
Photo (7)
Movie (7)
Music (13)
Leisure Sports (10)
Funny (5)
Hardware (119)
Software (120)
Windows (5)
Unix & Linux (119)
Installation (4)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (805)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)
C# (28)
Network Programming (17)
Database Programming (12)
OpenGL / DirectX (13)
Multimedia Programming (0)
Game Programming (21)
Parallel Distributed Progra... (0)
Reverse Engineering (0)
Debugging (9)
Python (1)
Ruby (1)
Ruby on Rails (1)
QT (4)
GTK (0)
JSP (0)
PHP (6)
ASP.NET (6)
ASP (3)
Development (28)
Useful Library (2)
Data Modeling (0)
Database (105)
Oracle (4)
MSSQL (41)
MySQL (2)
Data Warehouse (2)
Data Mining (3)
Network (66)
Web (78)
DHTML (4)
XHTML (1)
Javascript (1)
CSS (1)
AJAX (9)
XML (11)
Flex (1)
Silverlight (3)
Security (91)
DoS (1)
Kernel (10)
Scanning (3)
Sniffing (0)
Spoofing (4)
Overflow (28)
Web (11)
Shell (10)
Format String (14)
Window (2)
Embedded (70)
Multimedia (27)
Mobile (14)
Graphic (24)
Management (633)
Knowledge (581)
Hadoop (0)

Notice

  • 메타 블로그 사이트에 등록
  • 새해 맞이 블로그의 변화
  • 블로그 명칭 변경
  • 도메인(www.webdizen.net) 구...
  • TEXTCUBE 1.6.1로 업그레이드...

Tags

  • 튜닝
  • Thread 사용
  • Calendar
  • 에어론 의자
  • SETI@home
  • char
  • 스킨스쿠버
  • Forensic
  • grep
  • 프로그래머의 조건
  • 사그라다 파밀리아
  • Handing
  • Scanner
  • 예측 작업
  • 파일 종류
  • Iterator
  • 제로브드XE
  • 올림픽 공원
  • 원격 네트워크
  • Data Types

Recent Articles

  • ASCII Code의 CRLF 제거 방법.
  • Hadoop 에서 c++ API 이용시....
  • Ubuntu Linux에서 Hadoop 구....
  • 내 심장을 한껏 뛰게한 "국가....
  • 스타 스키마 데이터베이스 설....

Recent Comments

  • ■ 온라인카지노 ▶ http://L....
    asdf 11/21
  • 그리고 혹시 해외여행자보험....
    kim 11/05
  • ★★실제 바다게임장과 똑같....
    asdf 11/04
  • sbsyama.co.to← 짱5000만당....
    asdf 11/04
  • ♡KicaZ??o(???) 바카라사....
    fdsf3fass 11/03

Recent Trackbacks

  • 파일 열기/저장하기 CFileDialog.
    은마군의 나태블록 02/11
  • World IT Show 2008.
    상우 :: Oranzie's BLOG 2008
  • cvs서버 설치하기.
    3인3색 2008
  • 속속 공개되는 Google Chart....
    PHP와 Web 2.0 2007
  • 마방진을 구하는 프로그램.
    Oranzie's BLOG 3 2007

Archive

  • 2009/09 (3)
  • 2009/08 (1)
  • 2009/03 (1)
  • 2009/02 (9)
  • 2009/01 (13)

Calendar

«   2009/11   »
일 월 화 수 목 금 토
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

Bookmarks

    • Administration
      • IIS.NET
      • NTFAQ
      • OS의 모든 것
      • 리눅스포털
    • Database
      • SQL Server Central
      • SQL Team
    • Development
      • .NET Heaven
      • ASP Alliance
      • ASP.NET 2.0
      • Bullog.net
      • C# Corner
      • C++ (C PlusPlus.com)
      • C++ Reference
      • CodeGuru
      • CodePlex
      • DebugLab
      • Dev Articles
      • Devpia
      • DotNet Junkies
      • DotNet Zone
      • Driver Online
      • GOSU.NET
      • HOONS 닷넷
      • Joinc 팀블로그
      • KOSR
      • MSDN Home Page
      • OSR Online
      • Sky.ph - 개발자 커뮤니...
      • TAEYO.NET
      • The Code Project
      • WindowsClient.net
      • 김상욱의 개발자 Side
      • 조인시 위키
    • Human Networks
      • belief21c's e-space
      • I think I can
      • Invisible Rover's Blog :D
      • Rodman®
      • ■ Feel So Good~! ■
      • 까만 나비
      • 나를 가꾸는 시간.
      • 나만의 즐거움~~!
      • 단녕
      • 상우 :: Oranzie's BLOG
    • Information Technology
      • Microsoft TechNet
      • 지디넷코리아 - 글로벌...
    • Security
      • FoundStone
      • milw0rm
      • NewOrder
      • OpenRCE
      • Phrack.org
      • Reverse Engineering b1...
      • Reverse Engineering Team
      • RootKit
      • SecurityFocus
      • SecurityXploded by Nag...
      • Wow Hacker
      • Zone-H
Textcube
Louice Studio Inc.
Powered by Textcube. Original designed by Tistory.