본문 바로가기

프로그래밍(TA, AA)/C C++

[시스템프로그래밍] 디버깅

디버깅은 버그를 없앤다는 의미로 벌레 중에서도 해충을 없애는 것을 뜻합니다. 이렇게 벌레를 없애는 것이 프로그램의 오류를 제거하는 것과 뜻이 같아진 이유는 다음과 같습니다. 초창기 컴퓨터는 지금처럼 작지 않았습니다. 지금은 초고밀도 칩을 사용하기 때문에 크기를 줄일 수 있었지만 이런 기술이 없었던 초창기에는 진공관을 사용하여 컴퓨터를 만들었습니다.

 

그래서 컴퓨터 한대의 크기가 웬만한 건물 크기와 같았는데 이 속에는 엄청난 양의 진공관이 사용되었습니다. 진공관의 수명이 그렇게 길지 않았기 때문에 컴퓨터 내부를 돌아다니며 진공관만 교체하는 사람도 있었습니다. 이때 이 사람들의 주요 임무 중 하나가 진공관 사이에 타죽은 나방같은 벌레를 제거하는 작업이었습니다. 이때부터 사용된 컴퓨터 용어가 시스템의 오류를 만드는 버그와 이를 제거하는 디버그였습니다.

 

프로그램이 원하는 대로 작동하지 않도록 만드는 잘못된 코드도 버그의 일종이 됩니다. 버그는 쉽게 찾을 수 있는 것도 있고, 평상시에는 보이지 않다가 특정 조건의 특정 작업 중에만 나타나서 찾기가 아주 어려운 것도 있습니다.

 

사실 버그가 없는 프로그램은 거의 없고 다만 그것을 최소화하고 일반적인 상황에서는 나타나지 않도록 조치를 취해둔 프로그램이 완성도가 높은 프로그램입니다. 유닉스 시스템 자체에도 알려진 버그와 알려지지 않은 버그가 있을 것이고 개발 도구 자체에도 숨은 버그가 있을 것입니다.

 

앞에서 버그를 제거하는 것을 디버그라고 했는데 디버그 작업을 행하는 것을 디버깅이라고 합니다. 프로그래밍 환경의 좋고 나쁨을 선정하는 기준 중에 하나가 좋은 디버깅 환경을 제공하느냐의 여부입니다. 유닉스는 메모리 번지나 변수의 할당을 체크할 수 있는 환경을 제공하고 있으며, 여러 종류의 디버깅 툴킷들이 제공되고 있습니다.

 

 

GDB


유닉스에서 사용하는 디버깅 툴로 dbx라는 것이 있습니다. gdb는 GNU에서 만든 디버깅 툴로써 dbx와 동일한 작업을 수행할 수 있는 무료 툴입니다. dbx보다 쉽게 구할 수 있고 사용법도 거의 유사하기 때문에 gdb를 이용하여 디버깅 작업을 수행해보겠습니다.

 

먼저, gdb는 다음과 같이 실행할 수 있습니다.

 

gdb [-help] [-nx] [-q] [-batch] [-cd=dir] [-f] [-b bps] [-tty=dev] [-ssymfile] [-e prog] [-se prog] [-c core] [-x cmds] [-d dir] [prog[core|procID]]

 

gdb는 프로그램을 줄 단위로 실행하거나 특정 지점에서 멈추도록 할 수 있습니다. 그리고 프로그램 수행 중간에 각각의 변수에 어떤 값이 할당되어 있는지 확인할 수 있습니다. 이때 확인뿐만 아니라 원하는 값을 변수에 할당한 후 어떤 일이 벌어지는지 검사할 수도 있습니다.

 

프로그램이 비정상적으로 종료하게 되면 core dump 파일인 core 파일을 남기게 되는데 gdb 프로그램을 이용하면 왜 core 파일이 만들어졌는지 체크할 수 있습니다. gdb로 C와 C++ 프로그램 등을 디ㅏ버깅 할 수 있는데, 디버깅이 가능하려면 컴파일시에 -g 옵션이 사용되어야 합니다.

 

Makefile 속에 -g 옵션이 사용되는데, 보통 프로젝트를 완전히 종료하기 전에는 -g 옵션을 넣어서 컴파일하고 디버깅하는 작업을 계속 수행하는 것이 좋습니다. 그리고 프로젝트가 완료된 뒤에는 -g 옵션을 제거한 후 다시 컴파일하거나 그냥 두어도 됩니다.

 

-g 옵션을 이용하여 만들어진 프로그램(실행파일)을 gdb와 함께 실행하면 디버깅 과정이 시작됩니다. 예를 들어 mainProgram이라는 실행 파일이 있다면 다음과 같이 실행합니다.

 

$ gdb mainProgram (또는 dbx mainProgram)

만일 mainProgram을 실행하다 core dump가 발생하면 다음과 같이 core 파일과 함께 gdb를 실행하도록 합니다.

 

$ gdb mainProgram core

gdb는 현재 실행중인 프로세스에 대해서도 디버깅 작업을 수행할 수도 있는데 이때는 프로세스의 ID를 이용하면 됩니다. 예를 들어 mainProgram의 PID가 1000이면 다음과 같이 합니다.

 

$ gdb mainProgram 1000

일단 gdb가 실행되고 나면 쉘이 gdb 모드로 변경이 되고 gdb가 가지고 있는 각종 명령어(예약어)를 실행시킬 수 있게 됩니다. gdb를 실행하기 위해 꼭 알아두어야 할 명령어들을 간단히 정리하면 다음과 같습니다.

 

 - help: 도움말 내용이 나온다. help와 함께 명령어 이름을 표기하면 해당 명령어에 대한 도움말이 나온다.

 - quit: gdb 프로그램이 종료되고 원래의 shell로 나간다.

 - run: 프로그램을 시작한다. 프로그램 실행시 필요한 인수가 있으면 함께 적어주면 된다.

 - break: 프로그램의 break 포인트(중단 지점)을 지정한다. break 포인트를 지정할 때는 소스 파일과 파일 내부의 내용을 함께 이용하면 된다.

 - print expr: 수식이 수행되는 경우 그값을 화면에 출력한다.

 - c: 프로그램의 중단 지점 이후부터 다음 중단 지점까지(혹은 끝까지) 프로그램이 실행된다.

 - step: 프로그램 중단 지점의 다음 행을 수행한다.

 - next: step과 같이 프로그램 중단 지점의 다음 행을 수행하지만 step과 달리 행 속에 있는 함수 안으로 들어가지 않는다.

 - bt: 프로그램의 스택을 trace한다.

 

그러면 이제 실제 프로그램을 실행해 보면서 gdb의 사용예를 보도록 하겠습니다. 먼저 Makefile에 -g 옵션을 넣도록 하겠습니다. 다음과 같이 기존의 내용을 바꾸도록 하겠습니다.

 

CFLAGS = -c -O -I/usr/local/inclue -I../include

=>

CFLAGS = -c -g -O -I/usr/local/include -I../include

 

그리고 버그를 포함시키기 위해 다음과 같이 main.c 파일을 변경해보겠습니다.