← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 13/21

ptrace와 hardware debug register

debugger가 task를 정지시키고 register set, breakpoint, single-step 상태를 읽고 쓰는 경로를 architecture regset과 hardware slot 관점에서 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 169 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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를 다시 설정할 수 있다.

지연 시간 관점remote task stop IPI와 scheduling, large vector regset materialization, instruction/data breakpoint exception 빈도가 debugger latency를 지배한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCRlength/alignment가 지원되지 않는 watchpoint, reserved control bit 또는 CPU별 slot 수 차이를 검증하지 않으면 예측하지 못한 debug exception이 발생한다.MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다.
x86-64DR0-DR7, TF flag와 XSAVE regsetDR7 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-Vtrigger module 또는 software breakpoint와 regset2-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

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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를 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractptrace stop이 task 실행을 멈추고 scheduler context와 pt_regs를 안정화한다. regset view는 general, FP/vector, TLS와 debug register를 ABI note type으로 노출하고 context switch 또는 return-to-user에서 hardware state를 설치한다.
arm64user_hwdebug_state와 DBGWVR/DBGWCR, DBGBVR/DBGBCRdebug monitor enable, OS lock과 single-step PSTATE bit를 user return 전에 정합하게 갱신한다.
x86-64DR0-DR7, TF flag와 XSAVE regsetresume 전에 TIF_BLOCKSTEP/TF, debugreg load와 ptrace signal 상태를 맞춰야 하며 kernel address breakpoint를 user가 만들지 못하게 제한한다.
RISC-Vtrigger module 또는 software breakpoint와 regsetSEPC instruction 길이, compressed instruction과 vector state의 lazy ownership을 ptrace read 전에 materialize해야 한다.
lifetime boundarytracer가 task reference와 ptrace relationship을 유지하는 동안 regset backing state가 유효하다. hardware breakpoint slot은 task에 속하지만 CPU register에는 해당 task가 실행되는 동안만 load된다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비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-64state 준비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-Vstate 준비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를 확인한다.

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개 줄에 각각 설명을 붙였습니다.

L2296 if (is_compat_task())

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L2297 return &user_aarch32_view;

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L2298 else if (is_compat_thread(task_thread_info(task)))

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L2299 return &user_aarch32_ptrace_view;

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L2300(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2301 return &user_aarch64_view;

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L2302}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2303(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2304long arch_ptrace(struct task_struct *child, long request,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L2305 unsigned long addr, unsigned long data)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L2306{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2307 switch (request) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L2308 case PTRACE_PEEKMTETAGS:

arm64 MTE tag처럼 공통 regset 바깥의 architecture state를 별도 request로 처리한다.

L2309 case PTRACE_POKEMTETAGS:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L2310 return mte_ptrace_copy_tags(child, request, addr, data);

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L2311 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2312(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2313 return ptrace_request(child, request, addr, data);

architecture 전용 request가 아닌 항목은 공통 ptrace core로 위임한다.

L2314}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2315(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2316enum ptrace_syscall_dir {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L2317 PTRACE_SYSCALL_ENTER = 0,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L2318 PTRACE_SYSCALL_EXIT,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L2319};

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2320(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2321static void report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L2322{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L2323 int regno;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L2324 unsigned long saved_reg;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L2325(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2326 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2327 * We have some ABI weirdness here in the way that we handle syscall

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2328 * exit stops because we indicate whether or not the stop has been

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2329 * signalled from syscall entry or syscall exit by clobbering a general

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2330 * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2331 * and restoring its old value after the stop. This means that:

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2332 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2333 * - Any writes by the tracer to this register during the stop are

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2334 * ignored/discarded.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2335 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2336 * - The actual value of the register is not available during the stop,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2337 * so the tracer cannot save it and restore it later.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2338 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2339 * - Syscall stops behave differently to seccomp and pseudo-step traps

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2340 * (the latter do not nobble any registers).

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2341 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L2342 regno = (is_compat_task() ? 12 : 7);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L2343 saved_reg = regs->regs[regno];

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L2344 regs->regs[regno] = dir;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L2345(blank)

빈 줄은 arm64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L2346 if (dir == PTRACE_SYSCALL_ENTER) {

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L2347 if (ptrace_report_syscall_entry(regs))

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L2348 forget_syscall(regs);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L2349 regs->regs[regno] = saved_reg;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L2350 } else if (!test_thread_flag(TIF_SINGLESTEP)) {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L2351 ptrace_report_syscall_exit(regs, 0);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L2352 regs->regs[regno] = saved_reg;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L2353 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L2354 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개 줄에 각각 설명을 붙였습니다.

L722(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L723#if defined CONFIG_X86_32 || defined CONFIG_IA32_EMULATION

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L724static const struct user_regset_view user_x86_32_view; /* Initialized below. */

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L725#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L726#ifdef CONFIG_X86_64

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L727static const struct user_regset_view user_x86_64_view; /* Initialized below. */

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L728#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L729(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L730long arch_ptrace(struct task_struct *child, long request,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L731 unsigned long addr, unsigned long data)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L732{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L733 int ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L734 unsigned long __user *datap = (unsigned long __user *)data;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L735(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L736#ifdef CONFIG_X86_64

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L737 /* This is native 64-bit ptrace() */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L738 const struct user_regset_view *regset_view = &user_x86_64_view;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L739#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L740 /* This is native 32-bit ptrace() */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L741 const struct user_regset_view *regset_view = &user_x86_32_view;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L742#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L743(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L744 switch (request) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L745 /* read the word at location addr in the USER area. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L746 case PTRACE_PEEKUSR: {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L747 unsigned long tmp;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L748(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L749 ret = -EIO;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L750 if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L751 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L752(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L753 tmp = 0; /* Default return condition */

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L754 if (addr < sizeof(struct user_regs_struct))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L755 tmp = getreg(child, addr);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L756 else if (addr >= offsetof(struct user, u_debugreg[0]) &&

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L757 addr <= offsetof(struct user, u_debugreg[7])) {

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L758 addr -= offsetof(struct user, u_debugreg[0]);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L759 tmp = ptrace_get_debugreg(child, addr / sizeof(data));

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L760 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L761 ret = put_user(tmp, datap);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L762 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L763 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L764(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L765 case PTRACE_POKEUSR: /* write the word at location addr in the USER area */

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L766 ret = -EIO;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L767 if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L768 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L769(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L770 if (addr < sizeof(struct user_regs_struct))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L771 ret = putreg(child, addr, data);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L772 else if (addr >= offsetof(struct user, u_debugreg[0]) &&

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L773 addr <= offsetof(struct user, u_debugreg[7])) {

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L774 addr -= offsetof(struct user, u_debugreg[0]);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L775 ret = ptrace_set_debugreg(child,

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L776 addr / sizeof(data), data);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L777 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L778 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L779(blank)

빈 줄은 x86-64 ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L780 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개 줄에 각각 설명을 붙였습니다.

L331 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L332 return 0;

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L333}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L334(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L335void ptrace_disable(struct task_struct *child)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L336{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L337}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L338(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L339long arch_ptrace(struct task_struct *child, long request,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L340 unsigned long addr, unsigned long data)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L341{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L342 long ret = -EIO;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L343(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L344 switch (request) {

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L345 default:

분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.

L346 ret = ptrace_request(child, request, addr, data);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L347 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L348 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L349(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L350 return ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L351}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L352(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L353#ifdef CONFIG_COMPAT

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L354static int compat_riscv_gpr_get(struct task_struct *target,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L355 const struct user_regset *regset,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L356 struct membuf to)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L357{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L358 struct compat_user_regs_struct cregs;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L359(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L360 regs_to_cregs(&cregs, task_pt_regs(target));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L361(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L362 return membuf_write(&to, &cregs,

이 함수가 ptrace와 hardware debug register 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L363 sizeof(struct compat_user_regs_struct));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L364}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L365(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L366static int compat_riscv_gpr_set(struct task_struct *target,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L367 const struct user_regset *regset,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L368 unsigned int pos, unsigned int count,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L369 const void *kbuf, const void __user *ubuf)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. ptrace와 hardware debug register의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L370{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L371 int ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L372 struct compat_user_regs_struct cregs;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L373(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L374 ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &cregs, 0, -1);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L375(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L376 cregs_to_regs(&cregs, task_pt_regs(target));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L377(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L378 return ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L379}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L380(blank)

빈 줄은 RISC-V ptrace와 hardware debug register 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L381static const struct user_regset compat_riscv_user_regset[] = {

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

watchpoint가 context switch를 넘는 과정

task A가 VA 0x7fff2000의 8-byte write watchpoint를 갖고 CPU0에서 CPU3으로 이동한다고 가정한다.

  1. stop/configuretracer가 A를 ptrace-stop시키고 architecture가 address, length, access type을 task debug state에 검증해 저장한다.
  2. switch-outCPU0은 A의 hardware slot을 disable하거나 다음 task state로 교체한다.
  3. switch-inCPU3은 A의 task state를 해당 CPU가 지원하는 slot/encoding으로 load한다.
  4. hitwrite가 발생하면 debug exception의 address와 reason을 SIGTRAP/ptrace event로 변환해 A를 다시 정지시킨다.

결론watchpoint는 process page table에 있는 표식이 아니라 task lifetime과 CPU-local debug register를 연결한 상태다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 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를 다시 설정할 수 있다.

02

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를 확인한다.

03

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을 본다.

04

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를 확인한다.

05

객체 수명과 소유권을 먼저 고정한다

tracer가 task reference와 ptrace relationship을 유지하는 동안 regset backing state가 유효하다. hardware breakpoint slot은 task에 속하지만 CPU register에는 해당 task가 실행되는 동안만 load된다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64length/alignment가 지원되지 않는 watchpoint, reserved control bit 또는 CPU별 slot 수 차이를 검증하지 않으면 예측하지 못한 debug exception이 발생한다.MDSCR_EL1, DBG*VR/CR, PSTATE.SS, task debug state, ESR debug class와 FAR를 확인한다.
x86-64DR7 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-V2-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

재현과 계측 절차

  1. GDB watchpoint를 건 뒤 task affinity를 반복 변경해 breakpoint register가 CPU migration에서 다시 load되는지 추적한다.
  2. SVE/AVX-512/RISC-V vector 값을 만든 task를 stop해 regset read 전후 lazy state materialization 비용을 측정한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.