들어가기 전에

예전 부터 나는 늘 생각해왔었다. 오픈 소스만으로 프로그램을 개발할 수 있을까? 하고 말이다. 하지만, 직접 필드에서는 거의 오픈소스를 찾아보기 어려웠다. 가장 큰 이유를 들자면, 바로 책임의 문제였다. 오픈소스의 경우에는 문제가 발생했을 시에 추가적인 지원등을 전혀 받을 수 없기 때문에 이익을 목적으로 하는 회사에서는 섯불리 오픈소스를 이용해서 개발하기가 매우 어려워 보였다.
이 문서에서는 리눅스 상에서 직접 프로그램을 만들고, 디버깅 하는 방법을 다룬다. 참고 서적으로는 한빛미디어에서 출간된 '유닉스, 리눅스 프로그래밍 필수 유틸리티' 을 참고했다.

사용할 유틸리티들

  1. vi
  2. gcc
  3. make
  4. gdb
  5. rpm

위의 프로그램들의 이용하는 방법에 대한 설명은 개발하는 순차적으로 할 생각이다.

'프로그램 작성 → 컴파일 → 디버깅 → 패키지'

여기서는 아주 간단한 예제 프로그램을 작성하여 컴파일한 후에 직접 소스(source) 와 RPM 패키지까지 직접 만들어 볼 것이다. 실제로 만들어 봄으로써
실제로 왜 configure, make, make install 을 해주어야 하는지? 또 어떤 일들이 벌어지는 지에 대해서 자세히 알 수 있었다. 마지막으로 테스트 환경은 다음과 같다.

OS REDHAT 9(설치시에 반드시 Development Package 선택)
작업경로 /study
프로그래밍에 필요한 파일 main.c , hello.c

/study 디렉토리가 없다면, 만들어 주자!

gcc 를 이용한 프로그램 작성 및 컴파일

우리의 목적은 어떤 식으로 오픈소스 환경에서 프로그래밍을 하는지 알아보기 위함이므로 복잡한 프로그램은 필요하지 않다. 또한 나의 프로그래밍 능력의 한계로 인해 기본 중에 기본이라고 할 수 있는 'hello world' 프로그램을 만들어 볼 것이다.
다음은 main.c 파일의 모습이다.

#include <stdio.h>
int main()
{
printf("hello\n");
return 0;
}

이제 컴파일을 해보자!

# gcc -W -Wall -O2 -o main main.c

생소한 컴파일 옵션들이 몇 개 보인다.

옵션 설명
-Wall 모든 경고 메세지를 출력
-W -Wall에서 제외된 16 가지 종류의 다른 경고 메세지를 출력
-O2 컴파일을 최적화 시킴
-o 오브젝트 코드 파일 명을 지정함

에러없이 컴파일 되었다면, 실행해 보자!
이번에는 두개의 파일로 이루어진 프로그램을 컴파일해 볼 것이다.
다음은 hello.c 파일의 내용이다.

#include <stdio.h>
 
void lovecall()
{
printf("how are you?\n");
}

main.c 파일을 아래와 같이 수정해야 한다.

#include <stdio.h>
 
void lovecall();
 
int main()
{
printf("hello world\n");
lovecall();
 
return 0;
}

이제 컴파일 해보자! 하나 이상의 파일로 구성된 프로그램을 컴파일 할 때는 아래와 같이 한다.

#gcc -W -Wall -O2 -o main main.c hello.c

컴파일 후 직접 실행해보면, 우리가 원하는 결과가 나옴을 알 수 있다. 이번에는 앞에 프로그램에 몇 가지를 추가해보자.
hello.c 파일을 아래와 같이 수정한다.

#include <stdio.h>
#include <math.h>     // 주의 깊게 볼 것
 
void lovecall()
{
double love;
 
for(love=0; sin(love)+2; love++)
printf("i love you.\n");
}

위와 같이 컴파일 해보자! 아래와 같은 에러가 날 것이다.

/tmp/ccZ00V9R.o(.text+0x1e): In function `lovecall':
: undefined reference to `sin'
collect2: ld returned 1 exit status

에러가 나는 이유는 위의 hello 소스에 보았듯이 sin 함수가 포함되어 있는 라이브러리가 컴파일시에 포함되지 않았기 때문이다. 제대로 된 컴파일을 위해서는 라이브러리를 추가해주어야 한다. 다음과 같이 말이다.

#gcc -W -Wall -O2 -o main main.c hello.c -lm

컴파일 옵션 '-lm' 이 바로 그것이다.

make 를 이용한 컴파일

make 를 사용하는 여러가지 이유가 있겠지만, 내가 생각하기로 가장 큰 이유는 편의성이다. 수 많은 파일로 구성된 프로그램의 경우, 앞에서 본 것과 같이 일일이 명령을 내려서 컴파일하기란 이만저만 귀찮은 것이 아니다. make 는 이런 수고를 덜어준다.

CC      = gcc
CFLAGS  = -W -Wall -O2
LDFLAGS = -lm
 
main : main.c hello.c
        $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)    // 처음에 TAB KEY
 
clean :
        @rm -rf *.o main          // 처음에 TAB KEY

Makefile 이란 이름으로 위의 내용을 입력한다. make clean, make 를 입력하면, 방금전에 입력했던 컴파일 명령이 자동으로 입력되면서 컴파일 된다.

gdb 를 이용한 디버깅

프로그래밍을 처음부터 잘할 수는 없다. 컴파일 도중에 오류가 난다면, 그 원인을 찾아야 하는 데 이때 유용하게 사용되는 것이 바로 gdb 이다. gdb 를 사용하기 위해서는 에러를 발생시켜야 한다. 일부러 에러를 만들기 위해, 프로그램을 다음과 같이 작성한다.
우선 main.c 이다.

#include <stdio.h>
 
void lovecall();
 
char *getname()
{
char name[128];
 
printf("input name:");
scanf("%s",name);
return name;
}
int main()
{
char *str;
 
str = getname();
printf("i like you.\n");
printf("%s\n", str);
//lovecall();
 
return 0;
}

컴파일하면, 아래와 같이 경고메세지가 뜬다. 하지만, 컴파일은 된다. ^^;

gcc -W -Wall -O2 -o main main.c hello.c -lm
main.c: In function `getname':
main.c:11: warning: function returns address of local variable

직접 실행해보자!!

#./main
input name:fat81
hello world!
??

분명히 'fat81' 이라는 글자가 나와야 함에도 불구하고, 출력결과는 엉뚱한 문자가 나왔다. 아무래도, 컴파일시 나왔던 경고 메세지가 마음에 걸린다. 이럴때 유용하게 사용되는 것이 바로 gdb 이다. gdb 를 사용하기 위해서는 아래와 같이 컴파일시에 -g 옵션을 추가해주어야 한다. 예전보다 오브젝트 파일의 용량이 많이 커졌음을 알 수 있다.

#gcc -W -Wall -O2 -g -o main main.c hello.c -lm

gdb 에도 많은 명령어들을 제공한다. 아래의 표를 참고하기 바란다.

명령 설명
list 프로그램 소스 출력
b 함수명 breakpoint 를 지정한 함수의 진입점에 설정
r(run) breakpoint 까지 프로그램 실행
display 변수명 지정된 변수의 내용을 출력
s 숫자 지정한 숫자만큼 step 씩 실행
info f 변수명 지정된 변수의 주소 값을 출력
x/s 주소값 지정된 주소값에 저장된 값을 출력

이제 gdb 를 사용해 볼 차례다.

#gdb main
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) list                <----- 명령 입력
8
9               printf("input name:");
10              scanf("%s", name);
11              return name;
12      }
13
14      int main()
15      {
16              char *str;
17              str = getname();
(gdb) b main            <----- 명령 입력
Breakpoint 1 at 0x804844c: file main.c, line 17.
(gdb) r                 <----- 명령 입력
Starting program: /study/main
Breakpoint 1, main () at main.c:17
17              str = getname();
(gdb) s 2               <----- 명령 입력
10              scanf("%s", name);
(gdb) s                 <----- 명령 입력
input name:fat81        <----- 문자 입력
12      }
(gdb) s                 <----- 명령 입력
main () at main.c:18
18              printf("hello world!\n");
(gdb) display str       <----- 명령 입력
2: str = 0x42130a14 " \t\023B c\001@°¼"
(gdb) s                 <----- 명령 입력
17              str = getname();
2: str = 0x42130a14 " \t\023B c\001@°¼"
(gdb) s                 <----- 명령 입력
18              printf("hello world!\n");
2: str = 0xbfffee90 "fat81"
(gdb)                   <----- 명령 입력
hello world!
19              printf("%s\n", str);
2: str = 0xbfffee90 " ? &icirc;\022B"
(gdb) s                 <----- 명령 입력

위에서 보면, 마지막 부분에 문제의 원인이 보인다.

printf("%s\n", str);  

str 에 저장된 값이 바로 전까지는 'fat81' 이라는 문자열이 정상적으로 들어있었다. 하지만, printf 문에서의 str 는 값이 엉뚱한 값이 들어있다.
일단 문제의 결론부터 얘기하자면, str 이 지역변수이기 때문에 lovecall() 함수가 실행될 때만 유효한 값이 저장된다. 하지만, lovecall() 함수가 실행이 끝나서 빠져나오는 순간 str 은 전혀 다른 하나의 변수로 존재한다.

프로그램 수정

지역변수로 야기되는 대표적인 문제인 데, 해결하기 위해서는 main.c 파일의 name 변수를 전역 변수(static) 으로 선언하면 된다.
다음은 수정한 후의 프로그램이다.

#include <stdio.h>
 
void lovecall();
 
char *getname()
{
        static char name[128];       ----> 수정한 부분
 
        printf("input name:");
        scanf("%s", name);
        return name;
}
 
int main()
{
        char *str;
        str = getname();
        printf("hello world!\n");
        printf("%s\n", str);
//      lovecall();
//      printf("hello world!\n");
        return 0;
}

컴파일 후에 실행해보면, 원하는 출력을 얻을 수 있다.

  • computer/programming/오픈소스를_이용한_프로그래밍.txt
  • Last modified: 3 years ago
  • by likewind