본문 바로가기
Stack 동작의 비밀과 실제 메모리 덤프

계속 해서 Stack과 함수 이야기. Stack이니 함수니 하면서 뜬구름 잡는 얘기만 어언 몇 번째. 이제는 진짜 함수와 Stack에 관한 이야기를 해 볼까 합니다. 왜 고수들 보면은 Stack을 뒤져 본다던가, Stack을 Back tracing한다는 듣도 보도 못한 용어를 으시대며 지들끼리 낄낄거리는 장면을 보신적 있겠지요? 우리도 이제 낄낄거릴 차례가 되었습니다.
지금 보는 Push, Pop랑 연결된 Stack은 Thumb mode의 Full Descending Stack이야요.
우리 어느 순간에 Software Stop했을 때의 우리 ARM target의 context를 들여다 볼게요.

N _  R0       FFFE  R8          0
Z Z  R1          0  R9          0
C C  R2       5074  R10         0
V _  R3   1E6C1A35  R11         0
I _  R4          0  R12         0
F _  R5          0  R13  1F6E92C8
T T  R6       FFFF  R14  1E6C1A1D
J _  R7   1F6E943C  PC   1E6C19BC
usr  SPSR           CPSR 60000030
Q _
A _  USR:           FIQ:
E _  R8          0  R8          0
     R9          0  R9   C5400100
0 _  R10         0  R10  1F62C9F0
1 _  R11         0  R11  F00898F0
2 _  R12         0  R12        DD
3 _  R13  1F6E92C8  R13         1
     R14  1E6C1A1D  R14  F0008BFC
                    SPSR       10
     SVC:           IRQ:
     R13  E000A880  R13  F008CD00
     R14  F0000000  R14  1DB55BF8
     SPSR 60000010  SPSR 60000010

     UND:           ABT:
     R13  60000010  R13  00100000
     R14  1DB55BF8  R14  1EFCE4BE
     SPSR 60000010  SPSR 20000030


이 Context를 보면서 무엇을 느낄 수 있을까요? 무엇을 느끼셨어요? 일단 CPSR을 보니, User Mode이고요, 현재 Thumb mode로 실행 중이에요. IRQ나 FIQ는 enable상태에고요. 그밖에 어디를 실행 중이었는지 알 수 있겠네요. 우선 현재 PC값을 보면 0x1E6C19BC를 가리키고 있지요? PC를 보면 어디를 실행하고 있었는지 알 수 있겠네요. 그 영역을 한번 보시죠.

addr/line__|code_____|label____|mnemonic________________|comment________
           |
           |word b_funct(word arg, word param)
       3958|{
ST:1E6C19B8|B570      b_funct:        push    {r4-r6,r14}
ST:1E6C19BA|B0B2                sub     sp,#0xC8
           |          int loop;
       3960|        word ret = 0;
ST:1E6C19BC|2400                mov     r4,#0x0
           |        word array[100];
           |
       3963|        for (loop=0; loop < 100; loop++)
ST:1E6C19BE|2200      mov  r2,#0x0
ST:1E6C19C0|466E                cpy     r6,r13
           |        {
       3965|                if (loop%2)
ST:1E6C19C2|07D3                lsl     r3,r2,#0x1F      ; r3,loop,#31
ST:1E6C19C4|D002                beq     0x1E6C19CC
       3966|                        array[loop] = arg;

.밑 줄 쳐진 주소가 0x1E6C19BC이네요. word b_funct (word arg, word param) 이라는 함수를 실행하던 중이네요? 그러면, 이 함수를 부른 녀석은 어느 녀석일까요? 궁금해 지지 않으세요? 그럼 이때 R13 (Stack Pointer를 봅시다)

N _  R0       FFFE  R8          0
Z Z  R1          0  R9          0
C C  R2       5074  R10         0
V _  R3   1E6C1A35  R11         0
I _  R4          0  R12         0
F _  R5          0  R13  1F6E92C8
T T  R6       FFFF  R14  1E6C1A1D
J _  R7   1F6E943C  PC   1E6C19BC
usr  SPSR           CPSR 60000030

아차~ 그걸 보기전에 일단 이 함수에 진입했을 때 어떠 Register값들을 Stack에 Push했는지 볼까요?

ST:1E6C19B8|B570 b_funct: push {r4-r6,r14}
ST:1E6C19BA|B0B2 sub sp,#0xC8

오, 함수 b_funct()에 진입 하자마자 r4~r6과 r14를 push했네요. 그런데!! 바로 밑에 보면
sub sp-0xc8을 한 흔적이 있습니다. 왜 sp를 0xc8을 뺐을까요? 그것의 비밀은 바로 local variable을 stack에 넣는다는 그런 전설이 여기서 밝혀 집니다. b_funct()함수를 자세히 보시면 함수 진입 점에 local variable로 word array[200]을 잡아 놨네요? 오호라!! stack에다가 이 array를 임시로 잡기 위해서 0xc8 (십진수로 200)을 예약 한 겁니다. stack pointer는 r4~r6, 그리고 r14를 push한 후에 200개의 자리를 더 잡아 놓은 거죠. 참고로, 이 시스템에서 사용하는 stack의 형태는 Full Descending이니까 더 작은 주소로 Stack이 자라나니까 stack pointer의 주소를 빼면 stack에 200만큼 더 쌓은 거나 다름 없는 거죠! 그럼 이렇게 stack에 잡은 local variable은 어떻게 사용하느냐! 그건 뒤에 계속 하시고요~

그러면 r4~r6과 r14값을 제대로 찾으려면 sp+200을 해주면 찾을 수 있겠네요.
0x1F6E92C8 + 0xC8의 메모리 상태를 보면.

d.dump 0x1F6E92C8+0xC8 = 0x1F6E9390

___address__|________0________4________8________C_0123456789ABCDEF
 SD:1F6E9390|>00000000 00000000 0000FFFF 1E6C1A1D ..............l.
 SD:1F6E93A0| 00000000 1E6C1A59 00005074 1C87DE49 ....Y.l.tP..I...
 SD:1F6E93B0| 1F6E93C8 1F42F291 00000000 00000000 ..n...B.........
 SD:1F6E93C0| 00000000 00000000 1F6E943C 1F3FB721 ........<.n.!.?.
 SD:1F6E93D0| 23CBF4FC 00005074 1E6C1A35 23CBF4FC ...#tP..5.l....#
 SD:1F6E93E0| 1F6E943C 00000000 1E6C1A35 1F6E943C <.n.....5.l.<.n.
 SD:1F6E93F0| 23CBF4FC 1F3FEF7B 00000002 1F6E943C ...#{.?.....<.n.
 SD:1F6E9400| 00000000 00000000 0000FF00 1F3FF095 ..............?.
 SD:1F6E9410| 00000001 22DBA07C 00005074 1F6E943C ....|.."tP..<.n.
 SD:1F6E9420| 00000000 00000000 00000000 1F3FF449 ............I.?.
 SD:1F6E9430| 00000001 1ED87865 00000000 23CB00C8 ....ex.........#
 SD:1F6E9440| 00005074 1E6C1A35 00000000 106C5074 tP..5.l.....tPl.
 SD:1F6E9450| 000000EF 23CBFB44 00000000 1F6E943C ....D..#....<.n.
 SD:1F6E9460| 23CBF4FC 00000004 1F3FB979 23CBF4FC ...#....y.?....#
 SD:1F6E9470| 1F6E943C 00000000 23CBF4FC 00000000 <.n........#....
 SD:1F6E9480| 1F3FC2A7 1F6E943C 1F3FE187 00000000 ..?.<.n...?.....
 SD:1F6E9490| 00000000 23CBF4FC 1EFE816F 23CBF4FC .......#o......#
 SD:1F6E94A0| 00000000 1F6E943C 1F3FC435 1F6E9410 ....<.n.5.?...n.
 SD:1F6E94B0| 1D98DF2D 00000000 2037B45C 00000001 -.......\.7 ....
 SD:1F6E94C0| 1F6E936C 00000001 1D98F315 0000002E l.n.............
 SD:1F6E94D0| 1EE89C9F 000000B1 00000000 1FFAE168 ............h...
 SD:1F6E94E0| 00000001 1F6E9370 0000001F 00000000 ....p.n.........
 SD:1F6E94F0| 1F6E9370 1F6E936C 00000001 1F6E9410 p.n.l.n.......n.
 SD:1F6E9500| 1F6E939C 1F6E9368 00000000 00000001 ..n.h.n.........

 

요렇게 생겼네요. 그러면 r4~r6, r14 값은 어떤 것일까...

___address__|________0________4________8________C_0123456789ABCDEF
SD:1F6E9390|>00000000 00000000 0000FFFF 1E6C1A1D ..............l.
                 r4       r5       r6       r14

요렇게 되겠네요. 0x1E6C1A1D 요 놈이 바로 r14 값 입니다. 자, 어렵게 r14를 찾아 왔는데 맞는지 한번 볼까요? context에서 r14값을 한번 봅시다.

N _  R0       FFFE  R8          0
Z Z  R1          0  R9          0
C C  R2       5074  R10         0
V _  R3   1E6C1A35  R11         0
I _  R4          0  R12         0
F _  R5          0  R13  1F6E92C8
T T  R6       FFFF  R14  1E6C1A1D
J _  R7   1F6E943C  PC   1E6C19BC
usr  SPSR           CPSR 60000030

 


어랏! 0x1E6C1A1D. 같네요! 그렇습니다. 우리 제대로 찾아왔어요. r4~r6, r14값을 제대로 찾아 왔스므니다. r14가 그냥 그 값이라는걸 알면서 왜 이렇게 어렵게 찾아왔냐고요? 놀리냐고요? 아니죠~ 그렇게 하면 딱 한번의 history만 찾아낼 수 있으니까 같은 방법으로 한 칸 더 위를 따라가 보려고 일부러 어렵게 찾아 와 봤습니다. 우리 이렇게 까지 찾아 왔는데 계속 history를 찾을 수 있어야 하지 않을까요?일단 어쨌거나, b_fucnt()함수를 부른 넘은 0x1E6C1A1D 근방입니다. 정확히 말해 b_funct()함수를 처리하고 돌아가서 계속 진행할 주소는 0x1E6C1A1D이니까 여기보다
한 line위가 되겠네요. 한가지 주의할 점은 이것이 Thumb mode이니까 0x1E6C1A1D-1 = 0x1E6C1A1C로 돌아가야 한다고 해석할 줄 알아야 한다는 점이에요!

addr/line__|code_____|label____|mnemonic________________|comment______________________________________________________|
           |word a_funct(word arg, word param)
       3985|{
ST:1E6C1A10|B510      a_funct:        push    {r4,r14}
           |        int localone, localtwo;
           |        word ret;
           |
       3989|        localone = (int)(arg<<1);
ST:1E6C1A12|0040                lsl     r0,r0,#0x1       ; arg,arg,#1
       3990|        localtwo = (int)(param>>1);
ST:1E6C1A14|0849                lsr     r1,r1,#0x1       ; param,param,#1
           |
       3992|        ret =  b_funct ((word)localone, (word)localtwo);
ST:1E6C1A16|B280                uxth    r0,r0            ; localone,localone
ST:1E6C1A18|FFCEF7FF            bl      0x1E6C19B8       ; b
ST:1E6C1A1C|4604                cpy     r4,r0
           |
       3994|        if (ret>100)
ST:1E6C1A1E|2C64                cmp     r4,#0x64         ; ret,#100
ST:1E6C1A20|D903                bls     0x1E6C1A2A
       3995|            message ("too big");
ST:1E6C1A22|A078                add     r0,pc,#0x1E0
ST:1E6C1A24|ED62F0AE            blx     0x1E7704EC
ST:1E6C1A28|E002                b       0x1E6C1A30
           |        else
       3997|            message ("appropriate");
ST:1E6C1A2A|A078                add     r0,pc,#0x1E0
ST:1E6C1A2C|ED5EF0AE            blx     0x1E7704EC
           |
       3999|        return ret;
ST:1E6C1A30|4620                cpy     r0,r4            ; r0,ret
       4000|}
ST:1E6C1A32|BD10                pop     {r4,pc}
           |


밑줄 쳐진 부분이 b_funct()함수를 실행하고 나서, 돌아올 주소니까 바로 위를 보시면 bl 0x1E6C19B8 에 b_funct()함수를 호출하는 부분이 있네요! 오호라 간단하네요. 찾았습니다. b_funct()함수를 부른 곳을! b_funct함수를 부르는 함수는 a_funct함수네요. 자랑스럽게도.
그러면 같은 원리로 a_funct함수를 부른 놈을 찾아 볼까요? 간단합니다. 일단 Stack을 다시 한번 들여다 보시죠.

___address__|________0________4________8________C_0123456789ABCDEF
SD:1F6E9390|>00000000 00000000 0000FFFF 1E6C1A1D ..............l.
SD:1F6E93A0| 00000000 1E6C1A59 00005074 1C87DE49 ....Y.l.tP..I...
SD:1F6E93B0| 1F6E93C8 1F42F291 00000000 00000000 ..n...B.........
SD:1F6E93C0| 00000000 00000000 1F6E943C 1F3FB721 ........

 

그럼, b_funct()가 불렸을 때 SP는 0x1F6E93A0을 가리키고 있었겠네요. 그 당시를 생생하게 상상해 BoA요. 그리고, a_funct()함수가 불렸을 때 Stack에 뭐뭐 넣었었나 한번 확인해 봅시다.

 

addr/line__|code_____|label____|mnemonic________________|comment______________________________________________________|
           |word a_funct(word arg, word param)
       3985|{
ST:1E6C1A10|B510      a_funct:        push    {r4,r14}
           |        int localone, localtwo;

가만히 들여다 보니 r4와 r14를 push했었네요. 그러면, r4와 r14는

SD:1F6E93A0| 00000000 1E6C1A59 00005074 1C87DE49
                r4      r14

이런 셈이겠네요! 역시나 Thumb mode였으니까 r14-1 (= 0x1E6C1A58)의 주소를 가보면, a_funct()함수를 부르고 다음 실행할 주소가 나오겠죠

 

_addr/line__|code_____|label____|mnemonic________________|comment
        4032|          arg_ex = Index;
 ST:1E6C1A50|4630                cpy     r0,r6            ; r0,Index
            |
        4034|          ret2 = a_funct(arg_ex, (word)data);
 ST:1E6C1A52|2101                mov     r1,#0x1
 ST:1E6C1A54|FFDCF7FF            bl      0x1E6C1A10       ; a
 ST:1E6C1A58|9003                str     r0,[r13,#0x0C]   ; arg_ex,[r13,#12]


0x1E6CA58 바로 한 칸 위를 보면 a_funct() 함수를 호출한 곳이 보입니다. 으흐흐! 간단하죠! Stack을 거꾸로 들여다 볼 수 있다는 건 소프트웨어의 과거를 들여다 볼 수 있다는 말과 똑같습니다.

어때요? 간단하죠?

여기에서도 알 수 있듯이, R13에 엮여 있는 주소에 뭔가를 push하고, pop한다는 걸 봤죠. R13에 주소를 넣기만 하면 그 주소부터 push/pop하면서 장난 친다는 사실! 꼭 기억해 주세요.

왜 Local 변수가 없어질 수 밖에 없는가? 에 대한 대답을 여기에서 찾을 수 있겠죠. 일반 Symbol과는 달리 Stack에 저장 되니까 개인 Address가 없는 거에요. 당연히 그러니까 쓰고 나면 없어지겠죠!

 

 
 
Linked at 친절한 임베디드 시스템 개발자.. at 2009/10/01 12:45

... ⓖ Stack 동작의 비밀과 실제 메모리 덤프 ... more

친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -