Linux v6.18.37 · 개념과 코드 읽기

ptrace와 hardware debug register

이 코드는 어떤 문제를 푸나요?

디버거가 보는 레지스터는 임의의 순간에 다른 CPU의 레지스터를 그대로 훔쳐 읽은 값이 아닙니다. 정지한 task의 저장 상태와 아키텍처별 regset ABI를 통해 읽습니다. arm64는 GPR·FPSIMD/SVE 등, x86은 GPR·xstate 등, RISC-V는 GPR·FP/Vector 등을 정의합니다. 아래는 RISC-V GPR 읽기와 쓰기 함수입니다.

읽을 범위: v6.18.37 · arch/riscv/kernel/ptrace.c · riscv_gpr_get / riscv_gpr_set 36–53행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

regset

일반 레지스터, FP 등 관련 상태를 사용자 ABI의 묶음으로 표현합니다.

저장된 pt_regs

task의 예외·시스템 호출 상태를 보관한 커널 메모리입니다. task_pt_regs가 그 위치를 찾습니다.

__user

포인터가 사용자 주소 공간을 가리킨다는 표시입니다. kbuf와 ubuf는 다른 입력 경로입니다.

처음 읽을 때

실제 CPU, task의 저장 프레임, 디버거의 버퍼를 각각 그려 값의 이동을 설명하십시오.

더 깊이 살펴볼 때

task 정지 동기화가 이 helper 밖에서 확보된다는 점과 일반 레지스터·FP 상태의 최신화 차이를 검토하십시오.

그림으로 보는 변화

ptrace와 hardware debug register의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 정지 상태 확보

상위 ptrace 경로가 대상 task의 상태를 안정화합니다.

화살표는 helper 호출 전에 필요한 순서입니다.

2. 읽기

pt_regs의 사용자 GPR 부분을 출력 membuf에 복사합니다.

커널 저장 프레임에서 디버거 응답 쪽으로 값이 이동합니다.

3. 쓰기

입력 regset의 지정 범위를 대상 pt_regs에 반영합니다.

CPU 레지스터를 즉시 원격 변경하는 동작은 아닙니다.

riscv_gpr_get / riscv_gpr_set를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

static int riscv_gpr_get(struct task_struct *target,
			 const struct user_regset *regset,
			 struct membuf to)
{
	return membuf_write(&to, task_pt_regs(target),
			    sizeof(struct user_regs_struct));
}

static int riscv_gpr_set(struct task_struct *target,
			 const struct user_regset *regset,
			 unsigned int pos, unsigned int count,
			 const void *kbuf, const void __user *ubuf)
{
	struct pt_regs *regs;

	regs = task_pt_regs(target);
	return user_regset_copyin(&pos, &count, &kbuf, &ubuf, regs, 0, -1);
}
static int riscv_gpr_get(struct task_struct *target,

대상 task의 일반 레지스터 regset을 읽는 콜백입니다. 정지·동기화 조건은 상위 ptrace 경로가 확보합니다.

			 const struct user_regset *regset,

어떤 regset ABI를 처리하는지 나타내는 서술자를 콜백 인수로 받습니다.

			 struct membuf to)

결과를 적을 membuf를 받습니다. 출력 버퍼의 위치와 남은 용량을 관리하는 추상화입니다.

	return membuf_write(&to, task_pt_regs(target),

task_pt_regs로 대상의 저장 프레임을 찾고 그 내용을 membuf로 복사합니다. 다른 CPU의 살아 있는 레지스터를 직접 읽는 명령은 아닙니다.

			    sizeof(struct user_regs_struct));

사용자 ABI가 정의한 일반 레지스터 구조체의 크기만 출력합니다. 커널 내부 pt_regs의 모든 부가 필드를 노출하는 것이 아닙니다.

static int riscv_gpr_set(struct task_struct *target,

이번에는 대상 task의 일반 레지스터 저장 상태를 바꾸는 콜백입니다.

			 const struct user_regset *regset,

쓰기 대상인 regset의 서술자를 받습니다.

			 unsigned int pos, unsigned int count,

사용자가 갱신하려는 바이트 시작 위치와 길이를 받습니다. 항상 전체 레지스터를 한 번에 바꾸는 요청만 있는 것은 아닙니다.

			 const void *kbuf, const void __user *ubuf)

입력의 출처가 커널 버퍼인지 사용자 버퍼인지 구분합니다. __user는 사용자 주소 포인터임을 나타냅니다.

	struct pt_regs *regs;

수정할 pt_regs의 위치를 보관할 포인터를 선언합니다.

	regs = task_pt_regs(target);

대상 task의 저장 프레임 주소를 얻습니다.

	return user_regset_copyin(&pos, &count, &kbuf, &ubuf, regs, 0, -1);

공통 regset 복사 helper가 pos/count와 입력 포인터를 갱신하며 요청 범위를 프레임에 씁니다. 실제 사용자 복귀는 이후 이 저장 상태를 바탕으로 진행합니다.

함께 생각해 볼 질문

task_pt_regs는 현재 CPU 레지스터 읽기 명령입니까?

아닙니다. task에 속한 저장 프레임의 메모리 주소를 얻습니다.

일반 레지스터와 FP regset은 같은 복사입니까?

아닙니다. FP 상태는 실제 CPU에 최신 값이 남아 있으면 저장 절차가 추가로 필요할 수 있습니다.

이 코드만으로 실행 중인 task를 안전하게 검사할 수 있습니까?

상위 ptrace 정지·동기화 조건을 함께 만족해야 합니다.

출처와 읽은 범위

Linux stable v6.18.37 · arch/riscv/kernel/ptrace.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑