TIL
2021 08 02 (월) - gdb 본문
[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
- 만약,
libft
가libcustom
에 있는 코드를 사용한다면,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 시키면 코드가 보임과 동시에 디버깅을 할 수 있다.
지원하는 모드는 아래와 같다.
- 소스코드
- 어셈블리
- 소스코드 + 어셈블리
- 레지스터
소스코드 모드는 보통 아래와 같이 생겼다. 아래와 같은 화면에서 디버깅을 진행하면 될 것이다.
무한루프 해결
디버거를 돌리면, 무한루프에 빠져 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;
}
n = atoi(argv[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 |