본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

[리눅스커널] 유저 프로세스의 레지스터 세트인 struct pt_regs 파악하기

리눅스 커널에서 sys_enter ftrace 이벤트를 키면 syscall_trace_enter() 함수가 호출된다.
syscall_trace_enter() 함수의 첫 번째 인자로 'struct pt_regs *regs' 가 전달되는데 이 정체를 확인해보자.
 
다음은 syscall_trace_enter() 함수의 구현부이다.
 
asmlinkage int syscall_trace_enter(struct pt_regs *regs, int scno)
{
current_thread_info()->syscall = scno;
 
if (test_thread_flag(TIF_SYSCALL_TRACE))
tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER);
 
syscall_trace_enter() 함수는 어디서 호출될까?
다음 코드와 같이 __sys_trace 레이블에서 syscall_trace_enter() 함수를 호출한다.
 
arch\arm\kernel\entry-common.S
01 80101154 <__sys_trace>:
02 80101154:   e1a01007    mov r1, r7
03 80101158:   e28d0008    add r0, sp, #8
04 8010115c:   eb002728    bl  8010ae04 <syscall_trace_enter>
 
위 코드에서 03번째 줄을 유심히 보자. 스택 포인터에 8을 더해 r0 레지스터에 저장한다.
 
__sys_trace 레이블은 누가 호출할까?
 
vector_swi 레이블의 첫 번째 코드를 보자.
 
   NSR:80107EE0|vector_swi:           sub     r13,r13,#0x48    ; r13,r13,#72
   NSR:80107EE4|                      stm     r13,{r0-r12}
   NSR:80107EE8|                      add     r8,r13,#0x3C     ; r8,r13,#60
   NSR:80107EEC|                      stmdb   r8,{r13-r14}^
   NSR:80107EF0|                      mrs     r8,spsr
   NSR:80107EF4|                      str     r14,[r13,#0x3C]
   NSR:80107EF8|                      str     r8,[r13,#0x40]
   NSR:80107EFC|                      str     r0,[r13,#0x44]
   NSR:80107F00|                      mov     r11,#0x0         ; r11,#0
   NSR:80107F04|                      mrc     p15,0x0,r12,c1,c0,0x0   ; p15,0,r12,c1,c0,0 (system control)
   NSR:80107F08|                      ldr     r10,0x80107FC0   ; r10,__cr_alignment
   NSR:80107F0C|                      ldr     r10,[r10]
   NSR:80107F10|                      teq     r10,r12
   NSR:80107F14|                      mcrne   p15,0x0,r10,c1,c0,0x0   ; p15,0,r10,c1,c0,0 (system control)
   NSR:80107F18|                      push    {r0-r3,r12,r14}
   NSR:80107F1C|                      bl      0x801DD5C8       ; trace_hardirqs_on
   NSR:80107F20|                      pop     {r0-r3,r12,r14}
   NSR:80107F24|                      cpsie   i
   NSR:80107F28|                      lsr     r9,r13,#0x0D
   NSR:80107F2C|                      lsl     r9,r9,#0x0D
   NSR:80107F30|                      adr     r8,0x80107FC4    ; r8,sys_call_table
   NSR:80107F34|local_restart:        ldr     r10,[r9]
   NSR:80107F38|                      push    {r4-r5}
   NSR:80107F3C|                      tst     r10,#0xF0        ; r10,#240
   NSR:80107F40|                      bne     0x80107F68       ; __sys_trace
 
스택에서 0x48 만큼 뺀다.
 
다음은 유저 프로세서의 커널 공간의 스택 최하단 주소의 메모리 덤프이다.
 
________address|_data________|value_____________|symbol
   NSD:DA81BFA8| C0 E8 17 A2  0xA217E8C0
   NSD:DA81BFAC| C8 E9 17 A2  0xA217E9C8
   NSD:DA81BFB0| 90 E8 17 A2  0xA217E890 //<<--[1]
   NSD:DA81BFB4| 00 00 00 00  0x0
   NSD:DA81BFB8| 00 00 00 00  0x0   
   NSD:DA81BFBC| 08 00 00 00  0x8                
   NSD:DA81BFC0| C0 E8 17 A2  0xA217E8C0
   NSD:DA81BFC4| C8 E9 17 A2  0xA217E9C8
   NSD:DA81BFC8| 90 E8 17 A2  0xA217E890
   NSD:DA81BFCC| B1 00 00 00  0xB1                
   NSD:DA81BFD0| 20 D5 0C A4  0xA40CD520
   NSD:DA81BFD4| 59 00 00 00  0x59                
   NSD:DA81BFD8| 00 CC 8F AB  0xAB8FCC00
   NSD:DA81BFDC| 58 AF 79 AB  0xAB79AF58
   NSD:DA81BFE0| 30 D5 0C A4  0xA40CD530
   NSD:DA81BFE4| 90 E8 17 A2  0xA217E890
   NSD:DA81BFE8| F3 DB CE AE  0xAECEDBF3
   NSD:DA81BFEC| 44 7A D1 AE  0xAED17A44
   NSD:DA81BFF0| 10 00 0F 20  0x200F0010
   NSD:DA81BFF4| 90 E8 17 A2  0xA217E890
   NSD:DA81BFF8| 00 00 00 00  0x0
   NSD:DA81BFFC| FC 02 00 00  0x2FC         
   NSD:DA81C000| 80 00 00 00  0x80    //<- 스택 최하단 주소
      
[1] 스택 최하단 주소인 DA81C000이니 디폴트 스택 주소는 DA81BFF0이다. 여기서 0x48를 빼면 DA81BFA8가 된다.
 
바로 vector_swi 레이블의 첫 줄의 코드가 실행하면 업데이트되는 코드이다.
 
   NSR:80107EE0|vector_swi:           sub     r13,r13,#0x48    ; r13,r13,#72
   NSR:80107EE4|                      stm     r13,{r0-r12}
 
그런데 __sys_trace 레이블에서 스택 주소에서 +0x8만큼 더한다.
 
arch\arm\kernel\entry-common.S
01 80101154 <__sys_trace>:
02 80101154:   e1a01007    mov r1, r7
03 80101158:   e28d0008    add r0, sp, #8
 
위 코드가 실행되면 결국 스택 주소는 DA81BFB0(DA81BFA8 + 0x8)이 된다.
 
프로세스의 메모리 덤프에서 유저 공간에서 실행된 레지스터 세트의 정보와 스택 주소는 다음과 같다.
 
________address|_data________|value_____________|symbol
   NSD:DA81BFA8| C0 E8 17 A2  0xA217E8C0 //<<--[1]
   NSD:DA81BFAC| C8 E9 17 A2  0xA217E9C8
   NSD:DA81BFB0| 90 E8 17 A2  0xA217E890 //<<--[2] __sys_trace 레이블이 실행된 후 업데이트 되는 스택 주소, struct pt_regs
   NSD:DA81BFB4| 00 00 00 00  0x0  // r1
   NSD:DA81BFB8| 00 00 00 00  0x0  // r2 
   NSD:DA81BFBC| 08 00 00 00  0x8  // r3               
   NSD:DA81BFC0| C0 E8 17 A2  0xA217E8C0 // r4   
   NSD:DA81BFC4| C8 E9 17 A2  0xA217E9C8 // r5  
   NSD:DA81BFC8| 90 E8 17 A2  0xA217E890 // r6
   NSD:DA81BFCC| B1 00 00 00  0xB1           // r7: 시스템 콜 번호     
   NSD:DA81BFD0| 20 D5 0C A4  0xA40CD520
   NSD:DA81BFD4| 59 00 00 00  0x59                
   NSD:DA81BFD8| 00 CC 8F AB  0xAB8FCC00
   NSD:DA81BFDC| 58 AF 79 AB  0xAB79AF58
   NSD:DA81BFE0| 30 D5 0C A4  0xA40CD530
   NSD:DA81BFE4| 90 E8 17 A2  0xA217E890
   NSD:DA81BFE8| F3 DB CE AE  0xAECEDBF3
   NSD:DA81BFEC| 44 7A D1 AE  0xAED17A44
   NSD:DA81BFF0| 10 00 0F 20  0x200F0010
   NSD:DA81BFF4| 90 E8 17 A2  0xA217E890
   NSD:DA81BFF8| 00 00 00 00  0x0
   NSD:DA81BFFC| FC 02 00 00  0x2FC         
   NSD:DA81C000| 80 00 00 00  0x80    //<- 스택 최하단 주소
 
 
커널에서 어떤 함수를 사용하면 이 정보를 파악할 수 있을까?
 
#define task_pt_regs(p) \
((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
 
task_pt_regs() 함수를 호출하면 되지 않을까?