Egloos | Log-in
F/OSS study
F/OSS study
[DWARF] Call Frame Information
gas: 2.20.1
gcc: 4.5
arch: x86_64


Call Frame Information (CFI)는 stack backtrace (unwinding)을 위한 정보를 말한다.
최근의 ABI들은 frame pointer register를 사용하도록 강요하고 있지 않으므로
(특히나 레지스터가 부족한 x86과 같은 아키텍처에서는) 성능을 위해 이를 다른 용도로 사용할 수 있지만
이 경우 스택 추적이 불가능하므로 디버깅에 어려움을 겪을 수 있다.

DWARF의 CFI는 이러한 문제를 해결할 수 있는 방안을 제공하는 기법으로
각 함수마다 stack frame에 대한 정보를 별도의 영역에 기록하여 디버거에 제공하게 된다.
GNU binutils에 포함된 as는 CFI와 관련한 여러 directive들을 지원하며
gcc는 어셈블러 출력 파일에 이러한 정보를 포함하여 생성한다.
(x86 (IA-32) 환경의 경우 x86_64와 달리 기본적으로는 CFI 관련 내용이 생성되지 않는 듯 하다.
이 경우에도 -g 옵션을 이용하면 다른 디버깅 정보와 함게 CFI도 생성된다.)

CFI에서는 현재 실행 중인 함수에 대한 frame의 시작 주소를 CFA (Canonical Frame Address)로 나타내는데
CFA는 스택 상의 위치를 가리키기 위해 '레지스터 + 오프셋'의 형태로 구성된 주소이다.
이와 관련된 assembly directive로는 다음과 같은 것들이 있다.

.cfi_def_cfa           <reg, ofs>
.cfi_def_cfa_register  <reg>
.cfi_def_cfa_offset    <ofs>

먼저 .cfi_def_cfa는 CFA를 계산하는 방법을 정의한다.
즉 인자로 주어진 레지스터(reg)의 값에 오프셋(ofs)를 더한 값이 현재 함수의 CFA이다.
이렇게 정의한 내용은 이후에 수정이 가능하며
앞서 정의한 레지스터 대신 다른 레지스터를 이용하여 계산하려면 .cfi_def_cfa_register를
앞서 정의한 오프셋 대신 새로운 오프셋을 이용하여 계산하려면 .cfi_def_cfa_offset을 호출하면 된다.

이렇게 CFA를 계산할 수 있다면 frame 내에 저장된 레지스터 값 정보도 추출할 수 있어야 한다.
이러한 정보를 지정하기 위해 다음의 directive를 이용하여 그 방법을 정의할 수 있다.

.cfi_offset    <reg, ofs>

이는 주어진 레지스터(reg)의 이전 값이 CFA + ofs 위치에 저장되어 있다는 것을 뜻한다.
x86_64 아키텍처의 경우 함수 호출 시 다음에 수행할 instruction의 위치(return address)를
스택에 저장하므로 CFI를 이용하여 이전 frame으로 한 단계 올라가려면
rip 정보를 저장된 return address로 설정해주면 될 것이다.
즉, rip 값을 복원하기 위해 다음과 같이 정의한다.

.cfi_offset  16 (rip), -8

스택은 아래 방향으로 증가하며 x86_64 아키텍처의 레지스터 크기는 8 바이트이므로
-8이라는 오프셋이 사용되었음을 알 수 있다.
또한 레지스터는 각각의 고유번호를 통해 접근한다는 것도 기억하자.
참고로 각각의 레지스터 번호는 다음과 같다. (정확한 레퍼런스는 찾지 못했다.. ;;)

0 : rax
1 : rdx
2 : rcx
3 : rbx
4 : rsi
5 : rdi
6 : rbp
7 : rsp
8 : r8
...
15 : r15
16 : rip

이제 실제 예제를 통해서 사용법을 익혀보도록 한다.
기본적인 hello world 프로그램을 컴파일하여 어셈블리어 출력을 보면 다음과 같다.

$ gcc -S hello.c
$ cat hello.s
    .file    "hello.c"
    .section    .rodata
.LC0:
    .string    "hello world!"
    .text
.globl main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident   "GCC: (GNU) 4.5.0"
    .section    .note.GNU-stack,"",@progbits

main 함수 부분을 보면 여러 CFI directive들이 사용된 것을 알 수 있는데
먼저 .cfi_startproc과 .cfi_endproc은 함수의 시작/끝 부분에 나오며
해당 함수에 대한 CFI 정보를 생성하도록 지시한다.

다음은 rbp 레지스터를 스택에 push한 후에 CFA 계산을 위한 오프셋을 16으로 변경하였다.
(.cfi_def_cfa_offset) 그리고 rsp의 값을 rbp에 저장하여 현재 스택 위치를 저장한다.
rbp (6번) 레지스터의 이전 값은 앞에서 push한 위치, 즉 CFA - 16에 저장되었으므로
.cfi_offset을 통해 이를 기록해 둔다.
또한 이제부터 rbp 레지스터를 통해 CFA를 계산하도록 변경한다. (.cfi_def_cfa_register)
이후의 instruction들은 CFA에 영향을 주지 않으므로 계속 수행하다가
함수를 종료하기 직전에 CFA 계산 규칙을 원상태로 (rsp + 8) 복구한다.

이렇게 생성된 CFI 정보는 .eh_frame이라는 섹션에 저장된다.
원래 DWARF 표준의 CFI는 .debug_frame이라는 섹션에 저장되었는데
LSB (Linux Standard Base)에서 이를 약간 확장하면서
예외 처리 (EH : Exception Handling) 를 위한 정보를 포함할 수 있도록 하였다.

위의 예제를 컴파일한 후 readelf 도구를 이용하여 .eh_frame에 저장된 CFI 정보를 확인할 수 있다.

$ gcc -c hello.c
$ readelf -wf hello.o
Contents of the .eh_frame section:

00000000 00000014 00000000 CIE
  Version:               1
  Augmentation:          "zR"
  Code alignment factor: 1
  Data alignment factor: -8
  Return address column: 16
  Augmentation data:     1b

  DW_CFA_def_cfa: r7 (rsp) ofs 8
  DW_CFA_offset: r16 (rip) at cfa-8
  DW_CFA_nop
  DW_CFA_nop

00000018 0000001c 0000001c FDE cie=00000000 pc=00000000..00000015
  DW_CFA_advance_loc: 1 to 00000001
  DW_CFA_def_cfa_offset: 16
  DW_CFA_advance_loc: 3 to 00000004
  DW_CFA_offset: r6 (rbp) at cfa-16
  DW_CFA_def_cfa_register: r6 (rbp)
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop

.eh_frame은 하나 이상의 CFI를 저장할 수 있는데
각 CFI는 하나의 CIE (Common Information Entry)와
하나 이상의 FDE (Frame Description Entry)로 구성된다.
CIE에는 CFA의 초기 규칙을 지정하고 FDE는 각 함수 별 CFA 변경 사항을 유지한다.

'DW_CFA_'로 시작하는 줄은 이러한 변경 사항들을 나타내는 instruction으로
gas의 CFI 관련 directive에서 본 것과 같은 의미를 가진다.
단, CPU instruction이 진행되는 위치(PC)를 추적하기 위해
DW_CFA_advance_loc instruction이 추가적으로 사용되었다.
(위의 예제에서 pushq와 movq instruction은 각각 1바이트, 3바이트를 차지한다.)

위의 경우 초기의 CFA 계산 규칙은 rsp + 8이며
r16 (rip) 레지스터는 CFA - 8 위치에 저장되어 있음을 알 수 있다.
다음 FDE는 main() 함수에 대한 것으로 directive로 지정한 정보들이 동일하게 들어있다.


=== 참조 문서 ===

by namhyung | 2010/06/30 00:36 | System | 트랙백(1) | 덧글(0)
트랙백 주소 : http://studyfoss.egloos.com/tb/5347548
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from at 2014/03/11 00:24

제목 : garcinia cambogia extract
line3...more

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

카테고리
General
Application
System
Kernel
Book
Tips
태그
documentation binutils x86 git vcs memory bash perf synchronization script CARM block-layer linux scm SMP algorithm gcc CAaQA3 blktrace glibc elf C build compiler awk kernel computer-architecture sed patch emacs
전체보기
이글루 파인더

최근 등록된 덧글
informsi yang bagus dan be..
by pordanaia at 08/05
Informsi yang bagus dan b..
by jamalsu at 08/01
as inforasi yang menarik http..
by jamalsu at 07/22
최근 등록된 트랙백
Tod's Ferrari Homme
by Tods Pas Cher,Kodak did ..
Mocassin Femme
by Mocassins Homme, I got so..
natural garcinia cambogia
by
rss

skin by jiinny


X