C++ 프로그래밍을 하면서 디버깅은 어떻게 해야 할까요?
윈도우에서는 Visual Studio 라는 IDE를 통해 수월하게 디버깅이 가능하지만, 리눅스에서는 그렇지 않습니다.
리눅스 터미널에서는 디버깅 시 GDB를 주로 이용하며, 오늘은 GDB 를 이용한 Thread 를 분석방법에 대해 알아보려고 합니다.
기초적인 사용방법에 대해서는 본문 끝에 링크를 달아두었으니 이전 포스팅을 참고 부탁드리겠습니다.
GDB
GDB 는 GNU 프로젝트 디버거 입니다. GDB는 프로그램이 어떻게 동작하는지 분석할 수 있도록 도와줍니다.
GDB 사용 전 이 점은 반드시 숙지하고 있어야 하는 부분이 있는데요, 바로 빌드 시 별도 옵션을 추가해야 하는 점 입니다.
C, C++ 코드를 빌드하면서 추후 gdb로 디버깅이 필요하다면 빌드 시 '-g' 옵션을 반드시 추가해 줘야 합니다.
-g Produce debugging information in the operating system’s native format (stabs, COFF, XCOFF, or DWARF). GDB can work with this debugging information.On most systems that use stabs format, -g enables use of extra debugging information that only GDB can use; this extra information makes debugging work better in GDB but probably makes other debuggers crash or refuse to read the program. If you want to control for certain whether to generate the extra information, use -gvms (see below).
Thread
스레드는 프로세스가 수행하는 작업의 흐름입니다.
한 프로세스 내에서 여러개의 스레드가 수행 가능하며 프로세스 작업의 성능개선을 목적으로 사용합니다.
스레드는 프로세스 내에서 힙, 데이터, 코드 영역을 공유하며 각 스레드는 독립적인 스택을 가지고 있습니다.
공유하는 메모리 영역으로 인해 IPC가 필요하지 않다는 점이 장점이나 임계 영역에서의 상호배제를 고려해야하기에 멀티스레드 프로그램 개발난이도가 싱글스레드보다 상대적으로 높은 편 입니다.
GDB command
스레드를 디버깅 하기 전에 기본적인 명령어를 알아보겠습니다.
thread <thread-id> :
현재 scope를 thread-id 로 이동합니다.
info threads :
스레드 정보를 출력합니다.
thread apply < thread-id-list | all > args :
thread 리스트에 명령어를 적용합니다.
이 외에도 스레드 분석을 위한 명령어들이 더 있습니다.
기본적인 gdb 명령이 익숙해지신 분들은 아래 링크를 참고하여 더 많은 기능을 사용해 보시기 바랍니다.
https://sourceware.org/gdb/current/onlinedocs/gdb#Threads
Thread 분석
이제 위에서 설명한 명령어를 이용하여 스레드를 분석해 보겠습니다.
스레드 2개를 만들어서 변수를 증가시키는 코드를 바탕으로 디버깅을 시작해 보겠습니다.
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void func()
{
for ( int i = 0; i < 100 ; i++ )
{
cout << i << endl;
sleep(1);
}
}
int main()
{
thread t1( func );
thread t2( func );
t1.join();
t2.join();
return 0;
}
빌드를 해 줍니다.
$ g++ -g threads.cc -pthread
실행하면 for loop 에서 숫자를 계속 증가시키며 stdout을 출력하는 것을 확인할 수 있습니다.
그 상태에서 다른 터미널을 이용하여 실행중인 프로세스에 gdb를 붙여보겠습니다.
gdb가 실행되면 기존 터미널에서의 출력이 멈추는 것을 확인가능합니다.
스레드 정보를 조회해 보면 다음과 같습니다.
thread id 가 1, 2, 3 이 출력되는데 1은 t1, t2 를 생성하는 스레드, t1은 thread id 2, t2 는 thread id 3임을 유추할 수 있습니다.
터미널에서 숫자출력이 46번에서 끝났는데요, thread 2에서 현재 변수가 어떤 상태인지 확인해 보면 다음과 같습니다.
thread 2 번에서 backtrace (bt) 명령으로 현재 위치로 이동한 뒤 func 함수가 실행되는 framce ( frame 3 ) 으로 이동해줍니다.
위에 이미지를 보면 frame 이동을 위하 f 3 명령을 이용했고, 현재 sleep(1) 을 실행하면서 대기 중인 상태임을 확인할 수 있습니다.
frame 3으로 이동했으면 이제 list명령어를 통해 코드를 출력해봅니다.
찾고있는 함수 func() 가 출력되는 것을 확인할 수 있으며 현재 프로세스는 for loop 내에서 sleep(1) 을 수행 중임을 알 수 있습니다.
그럼 현재 기준 ( sleep 실행 ) i 의 값은 얼마일까요?
터미널에서 출력되었던 46과 같은지 확인하기 위해 p (print) 명령을 실행해 보면 $1 = 46 이 출력됨을 알 수 있습니다.
gdb 를 이용하면 원하는 스레드로 이동하면서 확인이 필요한 변수 값을 출력할 수 있고, 변수 값들을 출력하면서 기대하는 값이 나오고 있는지, 잘못 된 메모리 주소를 참조하고 있지는 않은지 확인함으로써 스레드 디버깅을 하실 수 있습니다.
'CS' 카테고리의 다른 글
Standard stream의 유형 stdout, stderr 차이점과 활용 (0) | 2024.03.05 |
---|---|
리눅스 개발하면서 C++ 디버깅 방법 - gdb breakpoint(중단점) 활용 (0) | 2024.03.04 |
[makefile 활용] makefile로 코드빌드 하기 (1) | 2024.02.26 |
비전공자를 위한 프로그래밍 개념 - 프로세스와 스레드 (0) | 2024.02.24 |
알아두면 회사에서 인정받는 리눅스 명령어, ls (0) | 2024.02.19 |