본문 바로가기

시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리

arm32 - Unwind Idx/prolog 소개

TRACE32는 어떻게 각 함수마다 어떤 register를 stack에 push를 하고 지역 변수를 위해서 얼마 만큼의 공간을 확보하는지를 알까요? 
모든 함수의 prolog를 체크하여 위 과정을 수행할까요? 그러기에는 과정들이 매우 복잡하므로, Build 과정에서 위의 prolog 정보를 미리 저장해둬요. 
 
Linux Kernel의 Symbol Table중에서 __start_unwind_idx에서부터 __stop_unwind_idx 영역이 있는데요.
TRACE32명령창에 symbol.browse *unwind_idx 을 입력하면 해당 Symbol Table을 확인할 수 있어요.
symbol___________________|type_____________________|address__________________|
__origin_unwind_idx      |(static struct unwind_id.|     D:C1AF1A60--C1AF1A63
__start_unwind_idx       |(struct unwind_idx [0])  |               D:C1777DF0
__stop_unwind_idx        |(struct unwind_idx [0])  |               D:C17D1AB8
 
__start_unwind_idx 주소부터 Key/Value pair로 각 함수 마다 어떻게 unwinding을 해야 되는지에 대한 정보를 갖고 있는데요. 조금 더 구체적으로 확인해볼까요?
 
TRACE32의 명령창에 data.dump 0xC1777DF0 /dialog 을 입력해요. 
_____address|________0________4
NSD:C1777DF0|>7E988210 80B0B0B0
NSD:C1777DF8| 7E98820C 8022ABB0
NSD:C1777E00| 7E9884D0 80AAB0B0
NSD:C1777E08| 7E98856C 80B0B0B0
// .. 생략..
NSD:C17B12D8| 7F2BA10C 80B107A9
NSD:C17B12E0| 7F2BA438 80B107AF
(where)
__start_unwind_idx       |(struct unwind_idx [0])  |               D:C1777DF0
 
아래 계산식으로 실제 함수 코드 주소를 확인할 수 있어요.
함수의 주소 = 주소(1) + (Key(2) | 0x80000000)
주소(1)          Key(2)         Value(3)              함수의 주소
0xC1777DF0  7E988210     80B0B0B0            C0100000 
0xC1777DF8  7E98820C     8022ABB0           C0100004
0xC1777E00  7E9884D0     80AAB0B0           C01002D0
0xC1777E08  7E98856C     80B0B0B0            C0100374
// .. 생략..
C17B12D8    7F2BA10C    80B107A9            C0A6B3E4
C17B12E0     7F2BA438    80B107AF            C0A6B718
 
"d.l 0xC0A6B3E4", "d.l 0xC0A6B718" 명령어를 입력하니 정확히 해당 주소에 아래 함수 심볼이 위치해있어요.
   NSR:C0A6B3E4|E92D4037  limFTCleanup:      push    {r0-r2,r4-r5,r14}
   NSR:C0A6B718|E92D4FF7  limFTPrepareAddBssReq:    push    {r0-r2,r4-r11,r14}
 
unwinding을 위해서는 (3)Value (0x809B47AF) 를 해석하면 되는데요.
(3)Value 에는 아래 3가지 format 중에서 한 가지 형식을 취할 수 있어요.
(1) 0x1 이면, 이 함수는 enwind를 할 수 없음.
(3) bit 31이 0 이면, unwind table 의 내용이 pre31 offset에 위치해 있음
(3) bit 31이 1 이면 table entry 를 의미함
 
해당 커널 소스 코드를 열어볼까요?
nt unwind_frame(struct stackframe *frame)
{
unsigned long low;
const struct unwind_idx *idx;
struct unwind_ctrl_block ctrl;
// .. 생략..
 
if (idx->insn == 1)
/* can't unwind */
return -URC_FAILURE;
else if ((idx->insn & 0x80000000) == 0)
/* prel31 to the unwind table */
ctrl.insn = (unsigned long *)prel31_to_addr(&idx->insn);
else if ((idx->insn & 0xff000000) == 0x80000000)
/* only personality routine 0 supported in the index */
ctrl.insn = &idx->insn;
else {
pr_warn("unwind: Unsupported personality routine %08lx in the index at %p\n",
idx->insn, idx);
return -URC_FAILURE;
}
 
이제 limFTCleanup 심볼에 대해서 얼마나 스택 pop을 하는지  체크를 해볼까요?
주소(1)          Key(2)         Value(3)              함수의 주소
C17B12D8    7F2BA10C    80B107A9            C0A6B3E4
 
Stack Pop Size: 80B107A9 파싱
 
하위 3비트가 001이므로 해당 값은 0x1이군요.
0xA9 = 10101001
10101nnn ARM_EXIDX_CMD_REG_POP   Pop r4-r[4+nnn], r14
 
실제 코드에 가보니 계산한만큼 스택을 pop "pop     {r4-r5,r14}" 하네요
______addr/line|code_____|label_____________|mnemonic________________|comment
   NSR:C0A6B3E4|E92D4037  limFTCleanup:      push    {r0-r2,r4-r5,r14}
// .. 생략..
   NSR:C0A6B540|E8BD4030                     pop     {r4-r5,r14}
 
limFTPrepareAddBssReq 심볼은 
80B107AF = AF의 마지막 3비트가 111이니까, pc이전에 7개의 레지스터를 pop하는군요.
NSR:C0A6B718|E92D4FF7  limFTPrepareAddBssReq:    push    {r0-r2,r4-r11,r14}
// .. 생략..
   NSR:C0A6BDA0|E8BD8FF0                            pop     {r4-r11,pc}
 
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2