01 · QUESTION
무엇을 확인할 것인가
다른 CPU에서 실행하던 task의 live register를 ptrace가 일관되게 읽을 수 있는 근거와, user가 쓴 debug state를 hardware에 load하는 시점은 언제인가?
ptrace stop이 task 실행을 멈추고 scheduler context와 pt_regs를 안정화한다. regset view는 general, FP/vector, TLS와 debug register를 ABI note type으로 노출하고 context switch 또는 return-to-user에서 hardware state를 설치한다.
task-stop synchronization, memory-backed register image와 현재 CPU hardware owner를 구분한다. breakpoint exception entry는 trap을 signal/ptrace event로 바꾸며 tracer가 continue할 때 single-step bit를 다시 설정할 수 있다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCR | length/alignment가 지원되지 않는 watchpoint, reserved control bit 또는 CPU별 slot 수 차이를 검증하지 않으면 예측하지 못한 debug exception이 발생한다. | MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다. |
| x86-64 | DR0-DR7, TF flag와 XSAVE regset | DR7 reserved bit, noncanonical FS/GS base, invalid xstate header를 그대로 쓰면 #DB loop나 privilege boundary 위반이 생긴다. | DR0-DR7, RFLAGS.TF/RF, TIF flags, xfeatures, stopped pt_regs와 #DB DR6 reason을 본다. |
| RISC-V | trigger module 또는 software breakpoint와 regset | 2-byte compressed instruction을 4-byte로 가정한 breakpoint patch, icache sync 누락 또는 unsupported trigger encoding은 잘못된 위치에서 trap을 만든다. | SEPC, SCAUSE breakpoint code, instruction halfword, trigger CSR capability, vector state flags와 regset byte size를 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCR
- state
arch_ptrace()와 regset callbacks가 GPR, FPSIMD, SVE/SME, TLS, PAC mask와 hardware debug slot을 노출한다. task switch가 breakpoint control을 CPU debug register에 설치한다.- checkpoint
- MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다.
x86-64
- mechanism
- DR0-DR7, TF flag와 XSAVE regset
- state
- x86 ptrace는 user area와 regset을 통해 GPR, segment/base, xstate와 debug register를 제공한다. DR7 enable/type/length가 DR0-DR3 주소 slot을 제어하고 RFLAGS.TF가 single-step을 만든다.
- checkpoint
- DR0-DR7, RFLAGS.TF/RF, TIF flags, xfeatures, stopped pt_regs와 #DB DR6 reason을 본다.
RISC-V
- mechanism
- trigger module 또는 software breakpoint와 regset
- state
- GPR/FP/vector regset을 ELF note 형태로 노출하고
arch_ptrace()는 공통 request를 사용한다. single-step은 hardware trigger capability 또는 임시 breakpoint 방식과 연결될 수 있다. - checkpoint
- SEPC, SCAUSE breakpoint code, instruction halfword, trigger CSR capability, vector state flags와 regset byte size를 확인한다.
04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCR
arch_ptrace()와 regset callbacks가 GPR, FPSIMD, SVE/SME, TLS, PAC mask와 hardware debug slot을 노출한다. task switch가 breakpoint control을 CPU debug register에 설치한다.
원본 코드: arch/arm64/kernel/ptrace.c:2296-2354
2296 if (is_compat_task())
2297 return &user_aarch32_view;
2298 else if (is_compat_thread(task_thread_info(task)))
2299 return &user_aarch32_ptrace_view;
2300
2301 return &user_aarch64_view;
2302}
2303
2304long arch_ptrace(struct task_struct *child, long request,
2305 unsigned long addr, unsigned long data)
2306{
2307 switch (request) {
2308 case PTRACE_PEEKMTETAGS:
2309 case PTRACE_POKEMTETAGS:
2310 return mte_ptrace_copy_tags(child, request, addr, data);
2311 }
2312
2313 return ptrace_request(child, request, addr, data);
2314}
2315
2316enum ptrace_syscall_dir {
2317 PTRACE_SYSCALL_ENTER = 0,
2318 PTRACE_SYSCALL_EXIT,
2319};
2320
2321static void report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir)
2322{
2323 int regno;
2324 unsigned long saved_reg;
2325
2326 /*
2327 * We have some ABI weirdness here in the way that we handle syscall
2328 * exit stops because we indicate whether or not the stop has been
2329 * signalled from syscall entry or syscall exit by clobbering a general
2330 * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee
2331 * and restoring its old value after the stop. This means that:
2332 *
2333 * - Any writes by the tracer to this register during the stop are
2334 * ignored/discarded.
2335 *
2336 * - The actual value of the register is not available during the stop,
2337 * so the tracer cannot save it and restore it later.
2338 *
2339 * - Syscall stops behave differently to seccomp and pseudo-step traps
2340 * (the latter do not nobble any registers).
2341 */
2342 regno = (is_compat_task() ? 12 : 7);
2343 saved_reg = regs->regs[regno];
2344 regs->regs[regno] = dir;
2345
2346 if (dir == PTRACE_SYSCALL_ENTER) {
2347 if (ptrace_report_syscall_entry(regs))
2348 forget_syscall(regs);
2349 regs->regs[regno] = saved_reg;
2350 } else if (!test_thread_flag(TIF_SINGLESTEP)) {
2351 ptrace_report_syscall_exit(regs, 0);
2352 regs->regs[regno] = saved_reg;
2353 } else {
2354 regs->regs[regno] = saved_reg;라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 59개 줄에 각각 설명을 붙였습니다.
if (is_compat_task())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return &user_aarch32_view;이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
else if (is_compat_thread(task_thread_info(task)))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return &user_aarch32_ptrace_view;이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return &user_aarch64_view;이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
long arch_ptrace(struct task_struct *child, long request,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned long addr, unsigned long data)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
switch (request) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case PTRACE_PEEKMTETAGS:arm64 MTE tag처럼 공통 regset 바깥의 architecture state를 별도 request로 처리한다.
case PTRACE_POKEMTETAGS:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
return mte_ptrace_copy_tags(child, request, addr, data);이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return ptrace_request(child, request, addr, data);architecture 전용 request가 아닌 항목은 공통 ptrace core로 위임한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
enum ptrace_syscall_dir {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
PTRACE_SYSCALL_ENTER = 0,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
PTRACE_SYSCALL_EXIT,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int regno;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned long saved_reg;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* We have some ABI weirdness here in the way that we handle syscallLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* exit stops because we indicate whether or not the stop has beenLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* signalled from syscall entry or syscall exit by clobbering a generalLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* purpose register (ip/r12 for AArch32, x7 for AArch64) in the traceeLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* and restoring its old value after the stop. This means that:Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - Any writes by the tracer to this register during the stop areLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* ignored/discarded.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - The actual value of the register is not available during the stop,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* so the tracer cannot save it and restore it later.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - Syscall stops behave differently to seccomp and pseudo-step trapsLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* (the latter do not nobble any registers).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
regno = (is_compat_task() ? 12 : 7);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
saved_reg = regs->regs[regno];계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->regs[regno] = dir;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (dir == PTRACE_SYSCALL_ENTER) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
if (ptrace_report_syscall_entry(regs))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
forget_syscall(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
regs->regs[regno] = saved_reg;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
} else if (!test_thread_flag(TIF_SINGLESTEP)) {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ptrace_report_syscall_exit(regs, 0);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
regs->regs[regno] = saved_reg;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
regs->regs[regno] = saved_reg;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
x86-64 · Linux 6.18.37
DR0-DR7, TF flag와 XSAVE regset
x86 ptrace는 user area와 regset을 통해 GPR, segment/base, xstate와 debug register를 제공한다. DR7 enable/type/length가 DR0-DR3 주소 slot을 제어하고 RFLAGS.TF가 single-step을 만든다.
원본 코드: arch/x86/kernel/ptrace.c:722-780
722
723#if defined CONFIG_X86_32 || defined CONFIG_IA32_EMULATION
724static const struct user_regset_view user_x86_32_view; /* Initialized below. */
725#endif
726#ifdef CONFIG_X86_64
727static const struct user_regset_view user_x86_64_view; /* Initialized below. */
728#endif
729
730long arch_ptrace(struct task_struct *child, long request,
731 unsigned long addr, unsigned long data)
732{
733 int ret;
734 unsigned long __user *datap = (unsigned long __user *)data;
735
736#ifdef CONFIG_X86_64
737 /* This is native 64-bit ptrace() */
738 const struct user_regset_view *regset_view = &user_x86_64_view;
739#else
740 /* This is native 32-bit ptrace() */
741 const struct user_regset_view *regset_view = &user_x86_32_view;
742#endif
743
744 switch (request) {
745 /* read the word at location addr in the USER area. */
746 case PTRACE_PEEKUSR: {
747 unsigned long tmp;
748
749 ret = -EIO;
750 if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))
751 break;
752
753 tmp = 0; /* Default return condition */
754 if (addr < sizeof(struct user_regs_struct))
755 tmp = getreg(child, addr);
756 else if (addr >= offsetof(struct user, u_debugreg[0]) &&
757 addr <= offsetof(struct user, u_debugreg[7])) {
758 addr -= offsetof(struct user, u_debugreg[0]);
759 tmp = ptrace_get_debugreg(child, addr / sizeof(data));
760 }
761 ret = put_user(tmp, datap);
762 break;
763 }
764
765 case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
766 ret = -EIO;
767 if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))
768 break;
769
770 if (addr < sizeof(struct user_regs_struct))
771 ret = putreg(child, addr, data);
772 else if (addr >= offsetof(struct user, u_debugreg[0]) &&
773 addr <= offsetof(struct user, u_debugreg[7])) {
774 addr -= offsetof(struct user, u_debugreg[0]);
775 ret = ptrace_set_debugreg(child,
776 addr / sizeof(data), data);
777 }
778 break;
779
780 case PTRACE_GETREGS: /* Get all gp regs from the child. */라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 59개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#if defined CONFIG_X86_32 || defined CONFIG_IA32_EMULATIONKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
static const struct user_regset_view user_x86_32_view; /* Initialized below. */이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#ifdef CONFIG_X86_64Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
static const struct user_regset_view user_x86_64_view; /* Initialized below. */이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
long arch_ptrace(struct task_struct *child, long request,이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned long addr, unsigned long data)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
unsigned long __user *datap = (unsigned long __user *)data;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_X86_64Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/* This is native 64-bit ptrace() */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
const struct user_regset_view *regset_view = &user_x86_64_view;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/* This is native 32-bit ptrace() */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
const struct user_regset_view *regset_view = &user_x86_32_view;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (request) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
/* read the word at location addr in the USER area. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
case PTRACE_PEEKUSR: {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
unsigned long tmp;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ret = -EIO;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
tmp = 0; /* Default return condition */계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (addr < sizeof(struct user_regs_struct))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
tmp = getreg(child, addr);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else if (addr >= offsetof(struct user, u_debugreg[0]) &&이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
addr <= offsetof(struct user, u_debugreg[7])) {이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
addr -= offsetof(struct user, u_debugreg[0]);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
tmp = ptrace_get_debugreg(child, addr / sizeof(data));helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
ret = put_user(tmp, datap);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case PTRACE_POKEUSR: /* write the word at location addr in the USER area */hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ret = -EIO;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (addr < sizeof(struct user_regs_struct))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
ret = putreg(child, addr, data);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
else if (addr >= offsetof(struct user, u_debugreg[0]) &&이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
addr <= offsetof(struct user, u_debugreg[7])) {이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
addr -= offsetof(struct user, u_debugreg[0]);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
ret = ptrace_set_debugreg(child,저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
addr / sizeof(data), data);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case PTRACE_GETREGS: /* Get all gp regs from the child. */hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
RISC-V · Linux 6.18.37
trigger module 또는 software breakpoint와 regset
GPR/FP/vector regset을 ELF note 형태로 노출하고 arch_ptrace()는 공통 request를 사용한다. single-step은 hardware trigger capability 또는 임시 breakpoint 방식과 연결될 수 있다.
원본 코드: arch/riscv/kernel/ptrace.c:331-381
331 else
332 return 0;
333}
334
335void ptrace_disable(struct task_struct *child)
336{
337}
338
339long arch_ptrace(struct task_struct *child, long request,
340 unsigned long addr, unsigned long data)
341{
342 long ret = -EIO;
343
344 switch (request) {
345 default:
346 ret = ptrace_request(child, request, addr, data);
347 break;
348 }
349
350 return ret;
351}
352
353#ifdef CONFIG_COMPAT
354static int compat_riscv_gpr_get(struct task_struct *target,
355 const struct user_regset *regset,
356 struct membuf to)
357{
358 struct compat_user_regs_struct cregs;
359
360 regs_to_cregs(&cregs, task_pt_regs(target));
361
362 return membuf_write(&to, &cregs,
363 sizeof(struct compat_user_regs_struct));
364}
365
366static int compat_riscv_gpr_set(struct task_struct *target,
367 const struct user_regset *regset,
368 unsigned int pos, unsigned int count,
369 const void *kbuf, const void __user *ubuf)
370{
371 int ret;
372 struct compat_user_regs_struct cregs;
373
374 ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &cregs, 0, -1);
375
376 cregs_to_regs(&cregs, task_pt_regs(target));
377
378 return ret;
379}
380
381static const struct user_regset compat_riscv_user_regset[] = {라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 51개 줄에 각각 설명을 붙였습니다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
return 0;이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void ptrace_disable(struct task_struct *child)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
long arch_ptrace(struct task_struct *child, long request,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned long addr, unsigned long data)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
long ret = -EIO;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (request) {hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
default:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
ret = ptrace_request(child, request, addr, data);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_COMPATKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
static int compat_riscv_gpr_get(struct task_struct *target,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const struct user_regset *regset,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct membuf to)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct compat_user_regs_struct cregs;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
regs_to_cregs(&cregs, task_pt_regs(target));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return membuf_write(&to, &cregs,이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
sizeof(struct compat_user_regs_struct));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int compat_riscv_gpr_set(struct task_struct *target,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const struct user_regset *regset,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int pos, unsigned int count,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const void *kbuf, const void __user *ubuf)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
struct compat_user_regs_struct cregs;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &cregs, 0, -1);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
cregs_to_regs(&cregs, task_pt_regs(target));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static const struct user_regset compat_riscv_user_regset[] = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
05 · WORKED EXAMPLE
숫자로 검산하기
watchpoint가 context switch를 넘는 과정
task A가 VA 0x7fff2000의 8-byte write watchpoint를 갖고 CPU0에서 CPU3으로 이동한다고 가정한다.
- stop/configuretracer가 A를 ptrace-stop시키고 architecture가 address, length, access type을 task debug state에 검증해 저장한다.
- switch-outCPU0은 A의 hardware slot을 disable하거나 다음 task state로 교체한다.
- switch-inCPU3은 A의 task state를 해당 CPU가 지원하는 slot/encoding으로 load한다.
- hitwrite가 발생하면 debug exception의 address와 reason을 SIGTRAP/ptrace event로 변환해 A를 다시 정지시킨다.
결론watchpoint는 process page table에 있는 표식이 아니라 task lifetime과 CPU-local debug register를 연결한 상태다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
ptrace stop이 task 실행을 멈추고 scheduler context와 pt_regs를 안정화한다. regset view는 general, FP/vector, TLS와 debug register를 ABI note type으로 노출하고 context switch 또는 return-to-user에서 hardware state를 설치한다.
task-stop synchronization, memory-backed register image와 현재 CPU hardware owner를 구분한다. breakpoint exception entry는 trap을 signal/ptrace event로 바꾸며 tracer가 continue할 때 single-step bit를 다시 설정할 수 있다.
arm64: user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCR
arch_ptrace()와 regset callbacks가 GPR, FPSIMD, SVE/SME, TLS, PAC mask와 hardware debug slot을 노출한다. task switch가 breakpoint control을 CPU debug register에 설치한다.
debug monitor enable, OS lock과 single-step PSTATE bit를 user return 전에 정합하게 갱신한다. 디버깅할 때는 MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다.
x86-64: DR0-DR7, TF flag와 XSAVE regset
x86 ptrace는 user area와 regset을 통해 GPR, segment/base, xstate와 debug register를 제공한다. DR7 enable/type/length가 DR0-DR3 주소 slot을 제어하고 RFLAGS.TF가 single-step을 만든다.
resume 전에 TIF_BLOCKSTEP/TF, debugreg load와 ptrace signal 상태를 맞춰야 하며 kernel address breakpoint를 user가 만들지 못하게 제한한다. 디버깅할 때는 DR0-DR7, RFLAGS.TF/RF, TIF flags, xfeatures, stopped pt_regs와 #DB DR6 reason을 본다.
RISC-V: trigger module 또는 software breakpoint와 regset
GPR/FP/vector regset을 ELF note 형태로 노출하고 arch_ptrace()는 공통 request를 사용한다. single-step은 hardware trigger capability 또는 임시 breakpoint 방식과 연결될 수 있다.
SEPC instruction 길이, compressed instruction과 vector state의 lazy ownership을 ptrace read 전에 materialize해야 한다. 디버깅할 때는 SEPC, SCAUSE breakpoint code, instruction halfword, trigger CSR capability, vector state flags와 regset byte size를 확인한다.
객체 수명과 소유권을 먼저 고정한다
tracer가 task reference와 ptrace relationship을 유지하는 동안 regset backing state가 유효하다. hardware breakpoint slot은 task에 속하지만 CPU register에는 해당 task가 실행되는 동안만 load된다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
remote task stop IPI와 scheduling, large vector regset materialization, instruction/data breakpoint exception 빈도가 debugger latency를 지배한다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | length/alignment가 지원되지 않는 watchpoint, reserved control bit 또는 CPU별 slot 수 차이를 검증하지 않으면 예측하지 못한 debug exception이 발생한다. | MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다. |
| x86-64 | DR7 reserved bit, noncanonical FS/GS base, invalid xstate header를 그대로 쓰면 #DB loop나 privilege boundary 위반이 생긴다. | DR0-DR7, RFLAGS.TF/RF, TIF flags, xfeatures, stopped pt_regs와 #DB DR6 reason을 본다. |
| RISC-V | 2-byte compressed instruction을 4-byte로 가정한 breakpoint patch, icache sync 누락 또는 unsupported trigger encoding은 잘못된 위치에서 trap을 만든다. | SEPC, SCAUSE breakpoint code, instruction halfword, trigger CSR capability, vector state flags와 regset byte size를 확인한다. |
08 · LAB
재현과 계측 절차
- GDB watchpoint를 건 뒤 task affinity를 반복 변경해 breakpoint register가 CPU migration에서 다시 load되는지 추적한다.
- SVE/AVX-512/RISC-V vector 값을 만든 task를 stop해 regset read 전후 lazy state materialization 비용을 측정한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/ptrace.c:2296-2354
- x86-64arch/x86/kernel/ptrace.c:722-780
- RISC-Varch/riscv/kernel/ptrace.c:331-381
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.