stack back trace for arm Feb 22, 2008

예전에 작성한 arm backtrace에 관한 간단한 문서이다.

다음과 같은 테스트 프로그램을 만들어보자.


#include
#include
#include

void trace (void)
{
void *array[2]; int size;
size = backtrace (array, 2);
}

void dummy_function (void)
{
trace ();
}

int main (void)
{
dummy_function ();
return 0;
}


위의 프로그램을 컴파일 한후 역어셈 해보자. (동적 라이브러리 함수 호출과정의 설명을 생략하기 위하여 static으로 컴파일하였다.)

arm_xxx-gcc -o trace main.c -static
arm_xxx-objdump -D trace > trace.dis

trace.dis 파일을 보면 다음과 같이 trace 함수의 역어셈 결과를 볼 수 있다.

119 00008218 :
120 8218: e1a0c00d mov ip, sp
121 821c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
122 8220: e24cb004 sub fp, ip, #4 ; 0x4
123 8224: e24dd00c sub sp, sp, #12 ; 0xc
124 8228: e24b3018 sub r3, fp, #24 ; 0x18
125 822c: e1a00003 mov r0, r3
126 8230: e3a01002 mov r1, #2 ; 0x2
127 8234: eb001d28 bl f6dc <__backtrace>
128 8238: e1a03000 mov r3, r0
129 823c: e50b3010 str r3, [fp, #-16]
130 8240: e24bd00c sub sp, fp, #12 ; 0xc
131 8244: e89da800 ldmia sp, {fp, sp, pc}

여느 함수와 마찬가지로 스택 프레임을 만드는 코드는 같다. - 120 ~ 122
지역 변수를 위해 12바이트 공간을 할당하였다. - 123
r0에 array 배열을 위한 첫 주소를 지정한다 - 124 ~ 125
r1에 2를 지정하여 __backtrace 함수를 호출하기 전 인수를 모두 지정한다. - 126
브랜치


7899 0000f6dc <__backtrace>:
7900 f6dc: e1a0c00d mov ip, sp
7901 f6e0: e92dd810 stmdb sp!, {r4, fp, ip, lr, pc}
7902 f6e4: e24cb004 sub fp, ip, #4 ; 0x4
7903 f6e8: e24dd004 sub sp, sp, #4 ; 0x4
7904 f6ec: e1a0c000 mov ip, r0
7905 f6f0: e3a00000 mov r0, #0 ; 0x0
7906 f6f4: e24b4011 sub r4, fp, #17 ; 0x11
7907 f6f8: e24b200c sub r2, fp, #12 ; 0xc
7908 f6fc: e1500001 cmp r0, r1
7909 f700: a91ba810 ldmgedb fp, {r4, fp, sp, pc}
7910 f704: e59fe030 ldr lr, [pc, #48] ; f73c <.text+0x766c>
7911 f708: e1520004 cmp r2, r4
7912 f70c: 391ba810 ldmccdb fp, {r4, fp, sp, pc}
7913 f710: e59e3000 ldr r3, [lr]
7914 f714: e1520003 cmp r2, r3
7915 f718: 291ba810 ldmcsdb fp, {r4, fp, sp, pc}
7916 f71c: e5923008 ldr r3, [r2, #8]
7917 f720: e78c3100 str r3, [ip, r0, lsl #2]
7918 f724: e2800001 add r0, r0, #1 ; 0x1
7919 f728: e5923000 ldr r3, [r2]
7920 f72c: e243200c sub r2, r3, #12 ; 0xc
7921 f730: e1500001 cmp r0, r1
7922 f734: a91ba810 ldmgedb fp, {r4, fp, sp, pc}
7923 f738: eafffff2 b f708 <__backtrace+0x2c>
7924 f73c: 00069828 andeq r9, r6, r8, lsr #16


  1. backtrace 함수는 일반 함수와는 약간 달리 r4 레지스터를 stack에 추가로 보관한다. - 7901
  2. 지역변수를 위한 공간은 4byte 할당한다. - 7903
  3. trace함수가 인수로 넘겨준 array의 주소를 ip 레지스터에 저장한다. - 7904
  4. r0레지스터에는 0을 지정한다. 루프의 counter로 사용될 것이다. - 7905
  5. r4레지스터에 fp - 17의 값을 저장한다. 이것은 현재 스택 프레임보다 1 바이트 더 내려간 값으로 비교를 위해 사용된다. - 7906
  6. r2레지스터에 saved fp의 주소를 지정한다. - 7907
  7. r0과 r1을 비교하여, 즉 counter 값이 trace에서 넘겨준 counter값보다 같거나 크다면 정리하고 함수를 빠져 pop한다. - 7908 ~ 7909
  8. lr레지스터에 69828 값을 저장한다. 현재 pc는 0xf70c이다.여기에 48을 더하면 0xf73c이며 그곳에는 69828값이 들어 있다. - 7910
  9. r2와 r4를 비교한 후 r2가 r4보다 작다면 pop한다. - 7911~7912
  10. lr 을 저장되어 있는 주소에 있는 값을 가져와 r3에 저장한다. 이 주소는 69828이다. 69828의 주소는 __libc_stack_end의 라벨이 되어 있으며 image에는 0x0 값이 들어가 있지만 실행시에는 어떤 값으로 채워질 것이며(누가 채워줄까??) 이 값이 __libc_stack_end 값이 될 것이다. - 7913
  11. r2의 saved fp의 주소가 __libc_stack_end의 값보다 같거나 더 크다면 pop한다. - 7914 ~ 7915
  12. saved lr의 값을 r3레지스터에 저장한다. - 7916
  13. 인수로 넘어온 arrary의 주소 * count * 4의 주소에 r3 값을 저장한다. 즉 return address를 인수의 array에 저장하는 것이다. - 7917
  14. counter를 1 증가시킨다. - 7918
  15. saved fp에 있는 값을 r3에 저장한다. 즉 이전 스택 프레임의 주소를 r3에 저장하는 것이다. - 7919
  16. 마찬가지로 r3에서 12를 빼서 이전 스택 프레임의 saved fp의 주소의 값을 r2에 저장한다. - 7920
  17. counter의 값이 인수로 넘어온 counter값보다 크지 않다면 9번으로 돌아간다. 그렇지 않으면 pop한다.

1 개의 덧글:

Minchan Kim said...

일반 glibc library를 통해서도 할 수 있는 방법도 있다.

/* For supporting backtrace in glibc,
* you build -r dynamic option with linker
*/
void * array[10];
size_t size_;
char **strings;
size_t j;

size_ = backtrace(array, 10);
strings = backtrace_symbols(array, size_);

for ( j = 0; j < size_; j++) {
printf("%s\n", strings[j]);
}
free(strings);