Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
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 31
Archives
Today
Total
관리 메뉴

TIL

2021 08 02 (월) - gdb 본문

2021/일일 기록

2021 08 02 (월) - gdb

ililillllllliilli 2021. 8. 2. 23:18

[toc]

1. 학습 날짜 : 20210802(월)

2. 학습 시간

3. 학습 주제 : 다양함.

4. 동료 학습 방법 : 개인

5. 학습 목표 :

  • 자료구조
  • vscode debugger 사용법 익히기
  • gdb 사용법 익히기
  • vscode 사용법 익히기

6. 학습 내용 :

헤더파일 사용하는 이유

보통 프로젝트가 어느정도 규모가 되면 모듈화를 위해 함수를 여러 파일에 작성해놓게 된다. 타 파일에서 함수를 가져오기 위해서는 사용하고자 하는 파일에 코드를 복사/붙여넣기해야된다.

그럼 파일의 모듈화를 해놓은 이유가 없다,

가독성이 떨어진다.

따라서, include 만으로 코드를 파일의 상단에 넣어주는 역할을 컴파일러에게 맡김으로써 위 문제 2개를 해결한다.

상기 사실 외적으로, 사용하고자 하는 함수들의 프로토타입을 알 수 있게 해준다.

  • 파일의 모듈화를 위해
  • 가독성을 위해
  • 프로토타입의 명세를 위해

vscode debugger 사용법

l https://www.youtube.com/watch?v=7qZBwhSlfOo

vscode 단축키

파일

ctrl + p : 파일 오픈

코드 단축키

ctrl + shift + A : 블록 주석 토글

ctrl + / : 주석 토글

에디터 기능 단축키

ctrl + space : 자동완성

ctrl + shift + space : 함수 프로토타입

F12 : 정의로 이동

ctrl + shift + F10 : 정의 보여주기

shift + F12 : 레퍼런스 찾기

F2 : symbol 변경 (변수명 변경)

ctrl + shift + E : 사이드바

ctrl + shift + D : 디버그

ctrl + shift + tab : 파일 이동

ctrl + backtick : 터미널 on/off

쓸만한 단축키들 :

vim 단축키

에디터 이동

shift + { or } : 빈 라인으로 이동 - 앞 또는 뒤로

% : bracket 짝으로 이동

f : 다음 character를 찾아줌

t : 다음 character를 찾아줌

; : f, t 사용 이후 다음 character를 찾아줌

스크롤

ctrl + y, ctrl + e : 스크롤 한 라인

ctrl + u, ctrl + d : page up, page down

z<enter> : 스크롤 올림

zz : 스크롤 가운데로

zb : 스크롤 밑으로

디버거 사용법 (gdb)

튜토리얼 출처 : https://www.youtube.com/watch?v=bWH-nL7v5F4

튜토리얼 소스 : https://github.com/cbourke/ComputerScienceI/tree/master/hacks/hack9.0-files

튜토리얼 출처에서 설명하는 대로 진행하면서 gdb를 사용해 볼 것이다.

컴파일 단계

아래 플래그 사용하여 컴파일 할 것.

gcc -Wall -g <program.c>

g 플래그 : 컴파일하고 삭제되는 identifier, symbols 테이블을 컴파일 후에도 남겨놓는다.

컴파일 시 라이브러리 링크 플래그가 컴파일커맨드의 가장 뒤로 가야 하는 이유

튜토리얼 따라하다가 gcc -g -w -lm primesProgram-buggy.c 와 같이 컴파일하면 라이브러리를 찾지 못한다. -l옵션이 뒤로 가야 컴파일 된다.

  • 이유 : 링커의 동작과정과 관련있는데, 링커는 여러 archiving 된 라이브러리를 사용하여 링킹하고자 하는 파일을 링킹한다. 이 때, 링커는 컴파일 커맨드에 적힌 파일/라이브러리를 순서대로 look up 하며, undefined symbols를 찾는다. 만약, undefined symbols를 컴파일 커맨드에 적힌 현재 보고 있는 파일보다 앞에 적혀있는 파일/라이브러리에서 찾게 된다면, 성공적인 링킹이 이루어진다. 그렇지 않다면, 다음과 같은 에러를 띄운다.
    .c:71: undefined reference to sqrt링커는 순서대로 libm, libft, libpthread, libcustom, file.c 를 주시하며 작업할 것이다. libcustom에 있는 코드가 libft에 있는 코드를 활용한다면, libft에서 undefined symbols를 찾은 후, 링킹을 완료한 상태에 있다면 문제가 발생하지 않을것이다.
  • $ gcc -g -lm -lft -lpthread -lcustom -o output file.c
  • 만약, libftlibcustom에 있는 코드를 사용한다면, libft에 있는 파일은 아직 링커가 lookup 하지 않은 symbol을 활용하려고 하기 때문에, undefined symbols 에러를 띄운다.
  • 예시로 다음 명령어를 들 수 있다.
    라이브러리 libm, libft, libpthread, libcustom, file.c를 활용하여 output을 만드는 컴파일 명령어다.
$ gcc -g -lm -w primesProgram-buggy.c 
/tmp/cccRihSF.o: In function `isPrime':
/tut/gdb_tut/hacks/hack9.0-files/primesProgram-buggy.c:71: undefined reference to `sqrt'

아래 command line을 사용하면 제대로 컴파일 되면서 a.out이 output으로 생성된다.

gcc -g  -w primesProgram-buggy.c -lm

튜토리얼 소스에 있는 코드를 받아 실행한다. 초기 n개의 소수를 더하는 프로그램이다. 인수가 주어질 경우와 주어지지 않을 경우 다르게 동작한다.

자세한 코드 동작 방식은 코드에 나와있으며, 튜토리얼 목적에 맞게 버그가 많아 gdb로 디버깅을 해가면서 코드를 고쳐나간다.

1차 수정

컴파일 후에 프로그램을 처음 실행시키면, 무한루프를 도는지 아무 결과도 나오지 않는다.

./a.out 

gdb로 들어가 layout next명령어를 사용한다.

(gdb) layout next

TUI (layout)

gdb를 CLI에서 실행헀다면, 아래와 같은 화면이 보일 것이고, vs코드 디버거나 타 디버거와는 다르게 CLI로 디버깅을 해야함을 직감한다. 코드가 보이지 않는다.

$ gdb ./a.out
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
...
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...done.

run을 type한다. 정상적인 프로그램이라면, 그냥 프로그램이 종료될 것이고, 튜토리얼 프로그램은 비정상적이고, 무한루프를 돈다. 어떻게 breakpoint를 걸것인지도 모르고, 코드가 보이지 않는다.

gdb는 여러 모드를 지원해주는데, 그 모드들을 enable 시키면 코드가 보임과 동시에 디버깅을 할 수 있다.

지원하는 모드는 아래와 같다.

  1. 소스코드
  2. 어셈블리
  3. 소스코드 + 어셈블리
  4. 레지스터

소스코드 모드는 보통 아래와 같이 생겼다. 아래와 같은 화면에서 디버깅을 진행하면 될 것이다.

무한루프 해결

디버거를 돌리면, 무한루프에 빠져 gdb에서 실행하는 프로그램 역시 무한루프에 빠지고, SIGINT로 빠져나와야만한다. 이후, n 또는 next를 gdb CL에 입력해주어 다음 line으로 넘어간다.

계속 n을 입력해보아도, 해당 루프에서 빠져나오지 못하는 것을 확인 할 수 있다.

   │53      int* getPrimes(int n) {                                                                                                     │
   │54        int result[n];                                                                                                            │
   │55        int i = 0;                                                                                                                │
   │56        int x = 2;                                                                                                                │
   │57        while(i < n) {                                                                                                            │
  >│58          if(isPrime(x)) {                                                                                                        │
   │59            result[i] = x;                                                                                                        │
   │60            i++;                                                                                                                  │
   │61            x += 2;                                                                                                               │
   │62          }                                                                                                                       │
   │63        }                                                                                                                         │
   │64        return result;                                                                                                            │
   │65      }      

문제가 있는 것은 isPrime()일 것이므로 isPrime 함수에 들어가 isPrime의 동작을 확인해야 한다.

breakpoint가 isPrime에 걸렸을 때, step 명령어로

(gdb) step

isPrime 안으로 step in 하여 동작을 확인한다.

isPrime()안에서의 변수를 확인해봤다.

print x #또는 p x

print x 로 x의 값을 확인했는데, x=2로 정상적인 값이 들어갔는데, 2는 소수임에도 불구하고 x % 2 == 0이라는 조건에 걸려 isPrime이 false가 되어 루프에서 빠져나오지 못함을 확인했다.

   │68        if(x % 2 == 0) {                                                                                                          │
   │69          return 0;   
int isPrime(int x) {
  if(x % 2 == 0) {
    return 0;
  }
  for(int i=3; i<=sqrt(x); i+=2) {
    if(x % i == 0) {
      return 0;
    }
  }
  return 1;
}

버그를 인지하고, x % 2 == 0조건을 고쳐주고 컴파일, 링킹과정을 거쳐 gdb를 다시 실행시켰다.

2차 수정

(gdb) run
Starting program: /gdb_tut/hacks/hack9.0-files/a.out
^C
Program received signal SIGINT, Interrupt.
0x0000555555554842 in isPrime (x=4) at primesProgram-buggy.c:70

여전히 무한루프를 돌았기 때문에, SIGINT로 흐름을 끊고, 코드를 봤다. 디버거에서는 (x = 4)일 때 무한루프를 돈다고 했다.

x = 4 일 때, 무한루프를 도는것을 확인 했고 , 코드를 보니 문제 발생구간을 찾을 수 있었다.

int* getPrimes(int n) {
  int result[n];
  int i = 0;
  int x = 2;
  while(i < n) {
    if(isPrime(x)) {
      result[i] = x;
      i++;
      x += 2;                    // 문제 발생구간
    }
  }
  return result;
}

문제 발생 구간을 수정해줬다.

3차 수정

문제 발생 구간 수정 후, SEGFAULT가 난 것을 확인했다. 포인터가 0을 참조한다고 한다. 다시 gdb로 들어가, arr(배열) 안을 확인했다.

(gdb) run
Starting program:/hacks/hack9.0-files/a.out

Program received signal SIGSEGV, Segmentation fault.
0x0000555555554731 in sum (arr=0x0, n=10) at primesProgram-buggy.c:48

배열의 값을 확인하는 것은 print 변수명과는 살짝 다르다. 밑의 명령어로 배열을 확인한다.

(gdb) print *arr@<length of array>
# 예시 : *arr@10

# arr@10을 입력할 경우, arr 주소값만 확인할 수 있다.

배열을 확인했지만, 당연히 arr의 주소 0을 참조할 수 없다. (arr = 0, *arr로 0인 주소에 대해 역참조를 하는 상황)

getPrimes()안에서 result배열의 값을 마지막에 확인했을 때 , (gdb) p result $3 = {2, 3, 5, 7, 9, 11, 13, 15, 17, 19}처럼 정상값이 들어갔으나, prime의 배열을 참조하려고 하니, 주소값 0에 대한 참조를 하고 있었다.

(gdb) p primes
$4 = (int *) 0x0

scope를 벗어난 배열에 대해서 동적할당을 해주면 문제가 해결될 것으로 보인다.

  int result[n];

4차 수정

동적할당을 하여 코드를 변경한 후, 다시 ./a.out 으로 동작을 확인했고,

(gdb) run
Starting program: /a.out 
The sum of the first 10 primes is 19

2이상의 소수를 순서대로 10개를 더한 결과가 19가 나옴을 확인했다. 뭔가가 잘못되었고, 디버깅을 했다.

break main으로 breakpoint를 main에 걸어준 뒤 아래 breakpoint에서 prime을 확인했는데, 정상 결과가 print되었다.

│38        int s = sum(primes, n);   

# (gdb) p *primes@10
# $2 = {2, 3, 5, 7, 9, 11, 13, 15, 17, 19}

문제는 sum()에서 생긴 것으로 보인다. 아래 줄을 total += arr[i]로 변경해준 뒤 다시 프로그램을 돌렸다.

         total =+ arr[i]; 
/gdb_tut/hacks/hack9.0-files/a.out 
The sum of the first 10 primes is 21946
[Inferior 1 (process 5817) exited normally]
(gdb) 

정상적인 결과가 나옴을 확인할 수 있다.

5차 수정

고정 n인 10개를 확인했으니, 이젠 20 30 40.. 등 개수를 인자로 넣어 확인힌다.

gdb에서 set args 20으로 인자를 설정 가능하다.

인자가 20개일 때도 똑같이 21946을 반환함을 알 수 있으며, 다시 gdb로 돌아가 디버깅한다.

인자를 20으로 설정해줬다면,

p *argv@2로 확인한다.

(gdb) p *argv@2
$1 = {0x7fffffffe2ec "s/hack9.0-files/a.out", 0x7fffffffe320 "20"}

인자가 20으로 정확하게 들어온 것을 확인했는데, breakpoint가 atoi에 걸리지않는다.

문제점은 2가지 존재한다.

int main(int argc, char **argv) {

  int n = 10; //default to the first 10 primes
  if(argc = 2) {
    atoi(argv[2]);
  }
  int *primes = getPrimes(n);

  int s = sum(primes, n);
  printf("The sum of the first %d primes is %d\n", n, s);

  return 0;
}
  1. n = atoi(argv[2])로 교체가 필요했고
  2. if (argc = 2)의 조건문을 if (argc == 2)로 교체해주어야 했다.

문제가 발생했다는 것은, 변수값을 확인하고 print하면서 알았다.

그렇게 고쳐주고 난 뒤 재컴파일/링킹 후 프로그램을 실행시켰을때, 프로그램이 정상작동함을 확인했다.



학습에 도움된 사이트 :

주제 사이트 비고
gdb 사용법 https://www.youtube.com/watch?v=bWH-nL7v5F4  
  https://www.youtube.com/watch?v=sCtY--xRUyI  
vim tutorial https://www.youtube.com/watch?v=IiwGbcd8S7I&t=3247s  
vscode debugger tutorial https://www.youtube.com/watch?v=7qZBwhSlfOo  
gdb 맛보기 60sec https://www.youtube.com/watch?v=mfmXcbiRs0E  
gdb 사용법 https://m.blog.naver.com/sioni322/221568024841  

오늘 발견한 문제와 해결방안:

7. 학습 총평 :

8. 다음 학습 계획 :

  • 자료구조 공부 + gdb사용하면서 gdb에 익숙해지기
  • philosophers + 운영체제 정리
  • advanced gdb 사용법 정리

'2021 > 일일 기록' 카테고리의 다른 글

2021 08 12 (수) - 철권7 대회 조추첨 프로그램  (0) 2021.08.13
2021 08 01 (일)  (0) 2021.08.02
2021 06 19 (토)  (0) 2021.06.19
2021 06 18(금)  (0) 2021.06.19
pipex 개인 피드백  (0) 2021.06.12
Comments