gcc - warning: stack frame size of xxxx 이해하기

숲사람·2022년 3월 24일
0

UNIX & C

목록 보기
7/12

stack frame 은 함수 call이 발생할때 stack 저장공간에 쌓이는 데이터다. Activation record 라고 부르기도한다. stack frame 사이즈는 컴파일 타임에 계산된다. 로컬변수, 리턴addr, 매개변수 등의 사이즈로 계산한다. stack overflow 를 조금 더 예방하기 위해 OS는 stack frame 사이즈에 제약을 가할 수 있다. 예를 들어 리눅스 커널은 CONFIG_FRAME_WARN 에 적절한 값을 지정할 수있다(lib/Kconfig.debug). 컴파일 타임에 함수에서 stack frame사이즈를 줄이고 싶다면 malloc()등을 활용해 heap에 데이터를 저장하는 방법이 있다.

gcc에는 -Wframe-larger-than= 옵션이 있는데 지정된 사이즈를 넘어서면 warning을 생성한다. GCOV_KERNEL config를 사용할때 발생할수 있다. gcov는 함수에 특정코드를 삽입하므로 stack frame이 증가하는 것같다. 아래와 같이 실험 해보자.

  • main.c

int main(void) {
    char s[1024];
    return 0;
}
$ gcc -std=c99 -O0 -Wframe-larger-than=1 main.c
main.c: In function ‘main’:
main.c:4:1: warning: the frame size of 1040 bytes is larger than 1 bytes [-Wframe-larger-than=]
 }
 ^
$ gcc -std=c99 -O0 -Wframe-larger-than=2048 main.c
# No warning.

참고

gcov 관련 옵션이 어떻게 stack frame 사이즈를 증가시키는가?

gcov 는 소스코드 커버리지 분석 도구이며 GCC(GNU Compiler Collection)에 포함되어있다. gcc의 -fprofile-arcs -ftest-coverage 옵션으로 컴파일하면 커버리지 관련 코드가 instrumentation 되어 컴파일된다. 이때 코드 사이즈가 증가하는데, 실제로 어떻게 바뀌는지 실험 해보자. 먼저 간단하게 아래와같은 코드를 만들었다. func함수가 옵션에 따라 어떻게 달라지는지 확인해보면 될것이다.

  • func_call.c
#include <stdio.h>

int func(int val)
{
    return val++;
}

int main(int argc, char *argv[])
{
    func(11);
    return 0;
}
  • Makefile
all: 
    gcc -Wall  func_call.c

gcov_test:
    gcc -Wall -fprofile-arcs -ftest-coverage func_call.c

clean:
    rm -rf a.out *.o *.gcda *.gcno

옵션 없이 컴파일 했을때

objdump -d a.out 로 역어셈블 한것중에 func와 main만 발췌했다. 전체 라인수는 192라인이었다.

00000000004004d6 <func>:
  4004d6:    55                       push   %rbp
  4004d7:    48 89 e5                 mov    %rsp,%rbp
  4004da:    89 7d fc                 mov    %edi,-0x4(%rbp)
  4004dd:    8b 45 fc                 mov    -0x4(%rbp),%eax
  4004e0:    8d 50 01                 lea    0x1(%rax),%edx
  4004e3:    89 55 fc                 mov    %edx,-0x4(%rbp)
  4004e6:    5d                       pop    %rbp
  4004e7:    c3                       retq   

00000000004004e8 <main>:
  4004e8:    55                       push   %rbp
  4004e9:    48 89 e5                 mov    %rsp,%rbp
  4004ec:    48 83 ec 10              sub    $0x10,%rsp
  4004f0:    89 7d fc                 mov    %edi,-0x4(%rbp)
  4004f3:    48 89 75 f0              mov    %rsi,-0x10(%rbp)
  4004f7:    bf 0b 00 00 00           mov    $0xb,%edi
  4004fc:    e8 d5 ff ff ff           callq  4004d6 <func>
  400501:    b8 00 00 00 00           mov    $0x0,%eax
  400506:    c9                       leaveq 
  400507:    c3                       retq   
  400508:    0f 1f 84 00 00 00 00     nopl   0x0(%rax,%rax,1)
  40050f:    00 

옵션 포함 컴파일 했을때

일단 기본적으로 어셈블리 라인수가 엄청나게 늘었다. __gcov 로 시작하는 함수가 엄청많이 생겼다. 전체 라인수가 192라인에서 2610라인으로 늘어났다.

0000000000400cc6 <func>:
  400cc6:       55                      push   %rbp 
  400cc7:       48 89 e5                mov    %rsp,%rbp
  400cca:       89 7d fc                mov    %edi,-0x4(%rbp)
  400ccd:       8b 45 fc                mov    -0x4(%rbp),%eax
  400cd0:       8d 50 01                lea    0x1(%rax),%edx
  400cd3:       89 55 fc                mov    %edx,-0x4(%rbp)
  400cd6:       90                      nop
  400cd7:       48 8b 15 82 35 20 00    mov    0x203582(%rip),%rdx        # 604260 <__gcov0.func>
  400cde:       48 83 c2 01             add    $0x1,%rdx
  400ce2:       48 89 15 77 35 20 00    mov    %rdx,0x203577(%rip)        # 604260 <__gcov0.func>
  400ce9:       5d                      pop    %rbp 
  400cea:       c3                      retq   

0000000000400ceb <main>:
  400ceb:       55                      push   %rbp 
  400cec:       48 89 e5                mov    %rsp,%rbp
  400cef:       48 83 ec 10             sub    $0x10,%rsp
  400cf3:       89 7d fc                mov    %edi,-0x4(%rbp)
  400cf6:       48 89 75 f0             mov    %rsi,-0x10(%rbp)
  400cfa:       48 8b 05 4f 35 20 00    mov    0x20354f(%rip),%rax        # 604250 <__gcov0.main>
  400d01:       48 83 c0 01             add    $0x1,%rax
  400d05:       48 89 05 44 35 20 00    mov    %rax,0x203544(%rip)        # 604250 <__gcov0.main>
  400d0c:       bf 0b 00 00 00          mov    $0xb,%edi
  400d11:       e8 b0 ff ff ff          callq  400cc6 <func>
  400d16:       ba 00 00 00 00          mov    $0x0,%edx
  400d1b:       48 8b 05 36 35 20 00    mov    0x203536(%rip),%rax        # 604258 <__gcov0.main+0x8>
  400d22:       48 83 c0 01             add    $0x1,%rax
  400d26:       48 89 05 2b 35 20 00    mov    %rax,0x20352b(%rip)        # 604258 <__gcov0.main+0x8>
  400d2d:       89 d0                   mov    %edx,%eax
  400d2f:       c9                      leaveq 
  400d30:       c3                      retq   
  • 분석
    둘다 main에서 func 로 call하기 전에 sub $0x10,%rsp 를하는데 (스택포인터(rsp)를 rbp로부터 0x10만큼 늘림). 일단 parameter 사이즈는 늘어나지 않음.
    위 내용만 봤을때 스택프레임 자체는 증가하지 않는것 아닌지?.

  • 결론
    더 분석 필요.

References

If the registers have a % prefix → AT&T syntax → src, dst order.
Otherwise, unadorned registers → Intel syntax → dst, src order.

https://stackoverflow.com/questions/44684936/how-to-determine-if-the-registers-are-loaded-right-to-left-or-vice-versa

profile
기록 & 정리 아카이브 용도 (보다 완성된 글은 http://soopsaram.com/documentudy)

0개의 댓글