Linux/Debugging & Testing

[gdb] The GNU Debugger : 3. Stack 정보 분석하기

Linuxias 2018. 8. 15. 01:33
반응형

 

스택은 현재 프로세스가 실행 중인 코드가 어디인지 확인할 수 있기에 매우 중요한 정보라고 할 수 있습니다. gdb에서는 종료되지 않은 함수를 하나의 frame이라고 합니다. 각 프레임은 스택에 쌓여있는 함수, 아직 종료되지 않은 함수라고 보시면 됩니다. 각 프레임은 Caller와 Callee의 관계를 구성하게 되죠. 
 
이번 예제는 아래 코드를 사용하겠습니다.
#include <stdio.h>
#include <stdlib.h>
 
void func4(int a, int b) { printf("sum : %d\n", a + b); }
 
void func3(int a, int b) { return func4(a, b); }
 
void func2(int a, int b) { return func3(a, b); }
 
void func1(int a, int b) {     return func2(a, b); }
 
int main(int argc, char *argv[])
{
    int a = atoi(argv[1]);
    int b = atoi(argv[2]);
 
    func1(a, b);
 
    return 0;
}
 
cs
 
  • STACK 정보 확인하기 (backtrace / bt)

특정 중단점에서 정보를 확인하고자 하려 할때 사용하는 명령어입니다. backtrace 또는 bt를 사용하여 정보를 확인할 수 있습니다. 이 정보는 스택 메모리를 기반으로 표시되게 됩니다. 위 예제 코드를 이용하여 분석을 시작해 보겠습니다. 먼저 중단점을 func4()로 설정하고 입력인자로 1 2를 전달하여 실행합니다. 그 후 backtrace 또는 bt 라는 명령어로 스택의 정보를 확인합니다. 그럼 제일 위에 func4() 함수부터 func4() <- func3() <- func2() <- func1() -< main() 함수 순서로 호출한 스택의 정보가 나타납니다. 여기서 각 #0, #1, .., #4가 앞에서 언급한 프레임이란 단위입니다.

 

gdb -q example2
Reading symbols from example2...done.
(gdb) b func4
Breakpoint 1 at 0x400574: file example2.c, line 4.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400574 in func4 at example2.c:4
(gdb) r 1 2
Starting program: /home/linuxias/privateProject/MyBox/example/gdb/example2 1 2
 
Breakpoint 1, func4 (a=1, b=2) at example2.c:4
4    void func4(int a, int b) { printf("sum : %d\n", a + b); }
(gdb) bt
#0  func4 (a=1, b=2) at example2.c:4
#1  0x00000000004005ad in func3 (a=1, b=2) at example2.c:6
#2  0x00000000004005cc in func2 (a=1, b=2) at example2.c:8
#3  0x00000000004005eb in func1 (a=1, b=2) at example2.c:10
#4  0x0000000000400637 in main (argc=3, argv=0x7fffffffda78) at example2.c:17
(gdb) 
 
 
 

좀 더 자세한 정보를 확인하기 위해 backtrace 또는 bt 명령어 뒤에 full을 붙여 실행해 보겠습니다.

 

(gdb) bt full
#0  func4 (a=1, b=2) at example2.c:4
No locals.
#1  0x00000000004005ad in func3 (a=1, b=2) at example2.c:6
No locals.
#2  0x00000000004005cc in func2 (a=1, b=2) at example2.c:8
No locals.
#3  0x00000000004005eb in func1 (a=1, b=2) at example2.c:10
No locals.
#4  0x0000000000400637 in main (argc=3, argv=0x7fffffffda78) at example2.c:17
        a = 1
        b = 2
 
cs

 

예제 코드는 매우 단순하여 상세한 내용이 보이진 않습니다. 지역변수가 있다면 No locals. 대신 해당 함수의 지역변수를 보여주게됩니다. main() 함수에는 a = 1, b = 2란 값이 있다는 걸 보여주고 있네요.

 

 

  • 프레임 정보 상세하게 확인하기 (info frame)

중단점 상태에서 각 프레임의 정보를 좀 더 자세히 알고 싶다면 info frame 명령어를 사용합니다.

 

 

 

 

 

 

(gdb) bt
#0  func4 (a=1, b=2) at example2.c:4
#1  0x00000000004005ad in func3 (a=1, b=2) at example2.c:6
#2  0x00000000004005cc in func2 (a=1, b=2) at example2.c:8
#3  0x00000000004005eb in func1 (a=1, b=2) at example2.c:10
#4  0x0000000000400637 in main (argc=3, argv=0x7fffffffda78) at example2.c:17
(gdb) info frame
Stack level 0, frame at 0x7fffffffd910:
 rip = 0x400574 in func4 (example2.c:4); saved rip = 0x4005ad
 called by frame at 0x7fffffffd930
 source language c.
 Arglist at 0x7fffffffd900, args: a=1, b=2
 Locals at 0x7fffffffd900, Previous frame's sp is 0x7fffffffd910
 Saved registers:
  rbp at 0x7fffffffd900, rip at 0x7fffffffd908
 
cs

 

info frame 명령어는 현재 위치한 frame의 정보를 알 수 있습니다. rip나 args의 정보 이전 프레임의 sp(스택 포인터) 등 다양한 정보들이 있네요, 레지스터나 메모리 주소에 대한 이해가 있다면 좀 더 상세한 분석을 할 수 있습니다. 그럼 다른 프레임의 정보는 어떻게 확인할 수 있을까요? 방법은 간단합니다. 각 프레임별로 번호가 있으니 info frame {frame number} 식으로 명령어를 실행하면 해당 프레임의 정보가 보입니다. main() 함수 즉, 4번 프레임의 정보를 보면 아래와 같습니다.

 

 

(gdb) info frame 4
Stack frame at 0x7fffffffd9a0:
 rip = 0x400637 in main (example2.c:17); saved rip = 0x7ffff7a2d830
 caller of frame at 0x7fffffffd970
 source language c.
 Arglist at 0x7fffffffd990, args: argc=3, argv=0x7fffffffda78
 Locals at 0x7fffffffd990, Previous frame's sp is 0x7fffffffd9a0
 Saved registers:
  rbp at 0x7fffffffd990, rip at 0x7fffffffd998
(gdb) 
 
 
 

 

위에 나타난 정보들이 어떤 의미인지 정리를 잠깐 하도록 하겠습니다.

 

Stack frame at 0x7fffffffd9a0

- 현재 스택 프레임의 베이스 주소로 저 주소부터 frame 4가 시작한다고 생각하시면 됩니다.

 

rip = 0x400637 in main (example2.c:17); saved rip = 0x7ffff7a2d830

- rip address pointer는 해당 프레임에서 실행될 다음 명령(instruction)의 주소 포인터입니다.

이 프레임이 main함수와 연관되어 있음을 확인할 수 있습니다. saved rip는 이전 프레임에서 다음에 실행될 명령 주소입니다.

 

caller of frame at 0x7fffffffd970

- caller of frame은 현재 프레임을 호출한 프레임을 나타냅니다. 즉 main()함수를 호출한 프레임이 되겠네요.

 

source language c.

- 이건 쉽죠? C로 작성되었다는 의미입니다.

 

Arglist at 0x7fffffffd990, args: argc=3, argv=0x7fffffffda78

- 함수의 variable 즉 변수의 시작을 나타내는 위치입니다. 어떤 변수들이 존재하는지는 나중에 알아보고 기본적으로 함수로

전달되는 argument의 정보는 알려주네요.

 

Locals at 0x7fffffffd990, Previous frame's sp is 0x7fffffffd9a0

- Locals는 지역변수의 시작 위치를 표시해 줍니다. Previous frame's sp는 말 그대로 이전 프레임의 스택포인터를 가리킵니다.

 

Saved registers: rbp at 0x7fffffffd990, rip at 0x7fffffffd998
- 현재 프레임의 rbp와 rip 레지스터의 값을 보여줍니다. rip는 instruction pointer이며 rbp는 stack base pointer입니다.

 

이 방법외에도 각 frame을 이동하는 명령어로 up, down이 있습니다. 프레임을 이동해갈 수 있는 명령어 입니다.

 

(gdb) up
#1  0x00000000004005ad in func3 (a=1, b=2) at example2.c:6
6    void func3(int a, int b) { return func4(a, b); }
(gdb) up
#2  0x00000000004005cc in func2 (a=1, b=2) at example2.c:8
8    void func2(int a, int b) { return func3(a, b); }
(gdb) down
#1  0x00000000004005ad in func3 (a=1, b=2) at example2.c:6
6    void func3(int a, int b) { return func4(a, b); }
cs

 

  • Stack memory 확인하기 (x/FMT Address)
스택 메모리를 직접 확인해 봅시다. 위에서 info frame 4를 확인했을 때 출력된 내용 중 아래와 같이 스택 프레임의 주소가 적혀 있는 것을 볼 수 있습니다.
Stack frame at 0x7fffffffd9a0
 
저 스택 주소에 어떠한 내용이 들어 있는지 살펴봅시다. 해당 내용을 살펴보기 위해서 x/FMT Address 명령어를 사용합니다.
FMT는 포맷이며, Address는 출력할 주소입니다. FMT에 들어갈 수 있는 내용은 아래와 같습니다.
    • x/o : octal
    • x/x : hex
    • x/d : decimal
    • x/u : unsigned decimal
    • x/t : binary
    • x/ f : float
    • x/a : address
    • x/i : instruction
    • x/c : char
    • x/s : string
    • x/z : hex, zero padded on the left
FMT에는 위와 같은 내용이 들어갈 수 있고 x/ 와 FMT 사이에 숫자를 적어주면 Address부터 그만큼의 size를 출력해줍니다. 한번 예시로 보죠. x, o, c, s, d, t, f, a, i, z 순서로 출력해보았습니다. 각 FMT는 여러분들이 분석하려는 내용의 포맷이 어떠한 형태인지 알고 있다면 매우 가독성 높은 정보를 제공할 것입니다. 지금은 어떠한 정보도 없이 main() 함수의 스택 주소를 이용해서 출력해보았는데요. 각 스택 내용을 분석하는 방법에 대해서는 이 글의 주제와 벗어나니 gdb 명령어만 소개드리고 지나가겠습니다. 기회가 된다면 메모리 구조와 분석방법에 대해 추가로 새로운 글을 작성하겠습니다. :D
(자세히 보시면 프로그램 실행 시 인자로 넘겨준 1, 2의 값이 main()의 스택에 올라가 있는게 보이네요 ^^)
 
(gdb) x/8x 0x7fffffffd910
0x7fffffffd910:    0x05ffda98    0x00000000    0x00000002    0x00000001
0x7fffffffd920:    0xffffd940    0x00007fff    0x004005cc    0x00000000
 
(gdb) x/8o 0x7fffffffd910
0x7fffffffd910:    0577755230    0    02    01
0x7fffffffd920:    037777754500    077777    020002714    0
 
(gdb) x/8c 0x7fffffffd910
0x7fffffffd910:    -104 '\230'    -38 '\332'    -1 '\377'    5 '\005'    0 '\000'    0 '\000'    0 '\000'    0 '\000'
 
(gdb) x/8s 0x7fffffffd910
0x7fffffffd910:    "\230\332\377\005"
0x7fffffffd915:    ""
0x7fffffffd916:    ""
0x7fffffffd917:    ""
0x7fffffffd918:    "\002"
0x7fffffffd91a:    ""
0x7fffffffd91b:    ""
0x7fffffffd91c:    "\001"
 
(gdb) x/8d 0x7fffffffd910
0x7fffffffd910:    -104    -38    -1    5    0    0    0    0
 
(gdb) x/8t 0x7fffffffd910
0x7fffffffd910:    10011000    11011010    11111111    00000101    00000000    00000000    00000000    00000000
 
(gdb) x/8f 0x7fffffffd910
0x7fffffffd910:    4.9729545178123994e-316    2.1219957919534036e-314
0x7fffffffd920:    6.9533558073448912e-310    2.0729947080329522e-317
0x7fffffffd930:    6.9533558073488437e-310    2.1219957919534036e-314
0x7fffffffd940:    6.9533558073464722e-310    2.0730100240679732e-317
 
(gdb) x/8a 0x7fffffffd910
0x7fffffffd910:    0x5ffda98    0x100000002
0x7fffffffd920:    0x7fffffffd940    0x4005cc <func2+29>
0x7fffffffd930:    0x7fffffffd990    0x100000002
0x7fffffffd940:    0x7fffffffd960    0x4005eb <func1+29>
 
(gdb) x/8i 0x7fffffffd910
   0x7fffffffd910:    cwtl   
   0x7fffffffd911:    (bad)  
   0x7fffffffd913:    add    $0x0,%eax
   0x7fffffffd918:    add    (%rax),%al
   0x7fffffffd91a:    add    %al,(%rax)
   0x7fffffffd91c:    add    %eax,(%rax)
   0x7fffffffd91e:    add    %al,(%rax)
   0x7fffffffd920:    rex fcos 
 
(gdb) x/8z 0x7fffffffd910
0x7fffffffd910:    0x0000000005ffda98    0x0000000100000002
0x7fffffffd920:    0x00007fffffffd940    0x00000000004005cc
0x7fffffffd930:    0x00007fffffffd990    0x0000000100000002
0x7fffffffd940:    0x00007fffffffd960    0x00000000004005eb
cs

 

위와 같이 출력함으로써 스택프레임에서 스택의 정보를 더 얻을 수 있습니다. 분석 능력만 갖추고 있다면 info frame 명령어를 이용해서 굉장히 많은 정보를 얻을 수 있을 거에요! 

 

 

[GDB]

2018/08/13 - [Linux/Debugging & Testing] - [gdb] The GNU Debugger : 1. Introduction

2018/08/14 - [Linux/Debugging & Testing] - [gdb] The GNU Debugger : 2. 중단점 설정하기

 

 

반응형