코어파일을 이용한 디버깅

코어파일이란 프로세스에 예외가 발생해 중지되는 순간의 프로세스 수행 이미지를 말한다. 이러한 코어 파일에는 메모리, 레지스터, 스택 등의 정보들로 구성된다. 이러한 코어 파일에는 애초에 디버깅 정보를 메모리에 로드하지 않았으므로 디버깅을 위한 정보가 들어가 있지 않다. 따라서 코어 파일 디버깅을 위해서는 코어파일과 함께 디버깅 정보가 들어있는 프로그램 실행 파일(main)을 함께 사용해야 한다.


코어 파일이 생성될 수 있는 예외 상황으로는 다음과 같은 것들이 있다.

  1. 키보드 종료 신호
  2. 잘못된 명령어 수행
  3. 디버깅을 위한 브레이크 포인트
  4. 비정상 오류
  5. 잘못된 메모리 참조
  6. CPU 시간 제한 초과
  7. 파일 크기 제한 초과
  8. 잘못된 시스템 콜

이러한 예외 상황이 발생할 경우 코어 파일은 자동으로 생기나 만약 생기지 않는다면 쉘에서 코어 파일의 크기가 0으로 설정되어 있는 경우이므로 이 때, 다음 명령어를 통해 코어 파일이 생길 수 있도록 해 두자.

$ unlimit -S -c 100000000


생성된 코어파일을 이용해 gdb를 수행하는 방법은 다음과 같다.

gdb {program_name} {core_file_name} : gdb main core.1408


이렇게 gdb를 실행하면 코어 파일에 있는 정보로 무엇 때문에 프로그램이 이상 종료했는지 출력하고, 심볼을 로드하고 어떤 함수에 문제가 있었는지 출력해 준다. 다음으로는 bt를 통해 스택프레임 정보를 확인하고 각종 명령어들을 사용하며 디버깅하면 된다.


실행중인 프로세스 디버깅

프로그램이 가끔 알지 못하는 이유로 오동작을 발생시킨다면 코어 파일도 없고 디버깅하기에 상당히 어려움을 줄 것이다. 이러한 경우에 gdb를 이용해 s 명령을 통해 계속 반복적으로 동작시키는 것은 상당한 시간 낭비일 것이다.

이러한 경우들에는 그냥 프로그램을 수행 시키고 오동작이 발생한 시점에 attach 명령으로 해당 프로그램에 gdb를 붙여 디버깅을 하면 된다.

사용되는 명령어는 다음과 같다.

$ attach {PID}

'빌드&테스트도구 > gdb' 카테고리의 다른 글

gdb(1) - gdb 기본 사용방법  (1) 2020.09.14

gdb란?

  • gdb는 GNU에서 만든 디버거이다.
  • gdb는 C, C++, Objective-C, Java, Go, Rust 등의 많은 언어를 지원하며, arm, x86, IA-64 등 수 많은 아키텍쳐를 지원한다.

gdb를 이용한 간단한 디버깅

아래와 같은 코드가 있다고 가정하자.

// main.c
#include <stdio.h>

~

위 코드를 다음과 같은 명령어로 컴파일하자.

gcc -g -o main main.c

  • gdb를 이용해 컴파일하려면 -g 옵션을 붙여야 한다. 이는 컴파일 시 실행파일에 여러 디버깅 정보(심볼 문자열, 심볼 주소, 컴파일에 사용된 소스파일, 인스트럭션에 해당하는 코드 정보 등)를 삽입하는 옵션이다.
  • -g 옵션을 주고 컴파일 시 최적화 옵션(-O 등)을 사용하지 않는 것이 좋다. 이는 코드 및 어셈블리 코드에 변형을 가하기 때문에 디버깅이 힘들어질 수 있다.

이 상태로 ./main 명령어를 이용해 실행한다면 오류가 발생하는 것을 확인할 수 있다.

실행 및 종료

gdb를 이용해 프로그램을 load하는 방법은 아래와 같다.

  • gdb {program_name} : gdb main
  • gdb {program_name} {core_file_name} : gdb main core.1408
  • gdb {program_name} {실행중인 PID} : gdb main 1408

gdb를 종료하는 방법은 아래와 같다.

  • q
  • [Ctrl] + d

gdb를 이용해 프로그램을 load한 상태에서 프로그램을 실행시키는 방법은 다음과 같다.

  • r
  • r arg1 arg2 : arg1과 arg2를 프로그램 인자로 실행
  • k : 프로그램 실행 종료

현재 지점에서 라인 단위, 또는 다음 브레이크 포인트까지 진행하는 명령어는 다음과 같다.

명령어 설명
s (step) 현재 행 수행후 멈춤.
함수호출 지점에서는 함수 내부로 들어감  
s 6 s를 6번 수행한 것과 같음
n (next) 현재 행 수행후 멈춤
함수 호출 지점에서는 함수 내부로 들어가지 않고 수행 후 다음 행으로 감  
n 6 n을 6번 수행한 것과 같음
c (continue) 다음 브레이크 포인트를 만날 때 까지 수행
u (until) 현재 loop문에서 빠져나올 때 까지 수행
for  
finish 현재 함수를 수행하고 빠져나감
return 현재 함수를 수행하지 않고 빠져 나감
return 1 현재 함수를 수행하지 않고 빠져 나감
return 값으로 1을 줌  
advance 20 현재 파일의 20번 라인을 만날 때 까지 진행
advance file.c:20 file.c 파일의 20번 라인을 만날 때 까지 진행

소스 보기

gdb를 이용해 프로그램을 load한 뒤 소스 코드를 보는 방법은 다음과 같다.

명령어 설명
l main 함수를 기점으로 소스 출력
l 10 Line number 10 을 기준으로 출력
l func func 함수의 소스를 출력
l - 출력된 행의 이전 행을 출력
l file.c:func file.c파일의 func 함수 부분을 출력
l file.c:10 file.c의 Line number 10 부근을 출력
set listsize 20 l 명령의 기본 출력 코드수를 20라인으로 설정

브레이크 포인트

프로그램 수행 시 특정 지점 또는 특정 조건에서 break하고자 하는 경우가 있다. 이 때 다음 명령어를 사용하면 된다.

명령어 설명
b func func 심볼의 시작 부분(함수 내부 진입 전)에 브레이크 포인트 설정
b 10 10번 라인에 설정
b file.c:func file.c 파일의 func 심볼에 설정
b file.c:10 file.c 파일의 10번 라인에 설정
b +2 현재 라인에서 2개 라인 이후 지점에 설정
b -2 현재 라인에서 2개 라인 이전 지점에 설정
b *0x8040000 0x8040000 주소에 설정(어셈블리 디버깅 시 사용)
b 10 if myVar == 0 10번 라인에서 myVar의 값이 0 일때 브레이크
rb func* func*에 해당하는 모든 심볼에 설정
rb ^fun fun으로 시작하는 모든 심볼에 설정
rb TestClass:: TestClass에 해당하는 모든 심볼에 설정

브레이크 포인트를 설정하게 되면 브레이크 포인트마다 고유한 숫자(1부터 시작)가 부여된다. 이 때, 해당 브레이크 포인트에 추가적인 조건을 걸고 싶을 때 다음과 같은 명령어를 사용하면 된다.

  • condition {brakpoint_ID} myVar == 0
  • condition {brakpoint_ID} myFunc(0) > 0 : myFunc(0)의 결과값이 0보다 클 때 브레이크
  • ignore {brakpoint_ID} 100 : 해당 브레이크 포인트가 100번 지나갈 때 까지 무시. 이는 loop문에서 유용하다.

이러한 브레이크 포인트는 gdb가 종료될 때 까지 유효하다. 따라서 한 번만 사용하는 브레이크 포인트는 b 명령어 대신 tb 명령어를 사용하면 된다.

브레이크 포인트 제거

명령어 설명
cl func func 함수의 시작 부분에 브레이크 포인트 지움
cl 10 10번 라인의 브레이크 포인트 지움
d 모든 브레이크 포인트를 지움
disable br 모든 브레이크 포인트 비활성화
disable br 1 3 1번, 3번 브레이크 포인트 비활성화
enable br 모든 브레이크 포인트 활성화
enable br 1 1번 브레이크 포인트 활성화
enable br once 1 1번 브레이크 포인트를 활성화하고, 한 번만 걸리고 비활성화되게 설정
enable br delete 1 1번 브레이크 포인트를 활성화 하고, 한 번ㅁ나 걸리고 제거되게 설정

현재 설정되어있는 브레이크 포인트를 보려면 다음의 명령어를 사용하면 된다.

  • info breakpoints, info b

와치 포인트 설정

특정 변수의 값이 바뀔 때 마다 브레이크를 걸려면 와치 포인트를 사용한다. 이 때, 지역변수에 대한 설정은 해당 지역 변수의 스코프 내에서 설정하여야 한다.
이는 다음과 같이 사용할 수 있다.

  • watch {myVariable} : myVariable에 값이 쓰여질 때 마다 브레이크
  • rwatch {myVariable} : myVariable의 값이 읽혀질 때 마다 브레이크
  • awatch {myVariable} : myVariable에 값이 쓰여지거나 읽혀질 때 마다 브레이크

변수 및 레지스터의 값 검사

명령어 설명
info locals 현재 스코프에서 모든 지역변수의 값들을 확인
info variables 모든 전역변수의 값들을 확인
Non-debugging 심볼들도 포함되어 출력됨.  
info (all-)registers 모든 레지스터(MMX 레지스터 포함)의 값을 확인한다.
p myVariable myVariable의 값을 출력
p *ptr  
p **ptr 포인터 변수의 경우 주소값이 아닌 해당 주소에 저장되어있는 값을 확인
p *ptr@4 ptr이 가리키는 것이 4개 크기의 배열일 경우 4개의 값을 모두 출력
p $eax 특정 레지스터의 값을 확인
p 'file.c'::myVariable 특정 파일의 전역변수(myVariable)을 확인
p myFunc::myVariable 특정 함수의 static 변수의 값을 확인
p (char *)ptr 특정 형식으로 변환해서 값을 출력
void *ptr = "abc"; 와 같은 경우  
p myVariable = 10 myVariable의 값을 10으로 설정
p ptr + 4 특정 byte-offset 위치를 지정하여 출력
p/{출력 옵션} myVariable 여기서 출력 옵션에 들어갈 수 있는 값을 다음과 같다.
- t: 2진수로 출력  
- o: 8진수로 출력  
-d: 부호가 있는 10진수로 출력  
-u: 부호가 없는 10진수로 출력  
-x: 16진수로 출력  
-c: 최소 1바이트 값을 문자형으로 출력  
-f: 부동 소수점 형식으로 출력  
display myVariable 변수의 값을 매번 디스플레이한다.
n, s 명령어를 통해 라인 단위로 실행하며 값을 확인하고자 할 때 유용  
display/{출력 옵션} myVariable 출력 옵션에 맞추어 값을 출력
p 명령어의 출력옵션과 동일  
undisplay {display_ID} 디스플레이 설정을 없앤다
disable display {display_ID} 디스플레이를 비활성화 한다
enable display {display_ID} 디스플레이를 활성화 한다

스택 프레임의 정보를 이용해 디버깅

보통 프로그램을 실행하여 특정 부분에서 오류가 발생해 종료된 경우 스택이 깨지지 않은 이상 스택 프레임은 종료될 당시의 상태를 유지한다. 이 때, bt(backtrace)명령어를 통해 전체 스택프레임 정보를 확인할 수 있다. 이와 관련하여 스택프레임에 대한 자세한 명령어들은 다음과 같다.

명령어 설명
info frame 스택 프레임의 정보를 출력
frame {frame_number} 특정 스택 프레임으로 변경
up 상위 스택 프레임으로 이동
up N N번 상위 스택 프레임으로 이동
down 하위 스택 프레임으로 이동
info args 함수가 호출될 때 인자를 출력
info locals 함수의 지역 변수를 출력
info catch 함수의 예외 핸들러를 출력

 

기타 유용한 명령어

어셈블리 코드 보기

  • disas myFunc: func 함수의 어셈블리 코드를 보여준다.

call 명령을 통한 함수별 모듈 테스트

  • call myFunc(123): 현재 상태에서 임의로 함수를 출력한다. 이는 리턴값을 추가로 출력해준다.

jump 명령을 통한 임의의 행, 주소, 함수로 분기

  • jump *0x0804000: 해당 주소로 무조건 분기
  • jump 10: 10번 라인으로 분기
  • jump myFunc: myFunc 함수로 분기

프로세스에 시그널 보내기

  • signal SIGKILL: 현재 수행중인 프로그램에 signal을 보낸다.

메모리 특정 영역에 값을 설정

  • set {{type}}{address} = {value}:
    이는 set {int}0x804000 = 100와 같이 사용되며 특정 메모리 영역에 값을 설정하게 해 준다.
    또한 이는 p *0x804000=100와 같은 결과를 가져다 준다.

gdb 환경 설정 관련 명령

명령 설명
info set set 명령으로 설정가능한 명령어들의 리스트를 보여준다.
set print array on array를 출력할 때 여러 줄로 출력해 준다.

$2 = {0x804000 "one",
0x804004 "two",
0x804008 "three",
0x0}
info functions 함수들의 리스트를 출력
info types 선언된 타입들을 출력

+ Recent posts