이 코드는 어떤 문제를 푸나요?
스택 추적은 메모리에서 주소처럼 보이는 값을 아무거나 모으는 작업이 아닙니다. arm64는 frame record와 별도 메타데이터, x86은 ORC 등의 unwind 정보, RISC-V는 설정에 따른 frame 규칙을 이용합니다. 대표로 arm64가 다음 frame record를 검증하며 걷는 함수를 읽습니다.
읽을 범위: v6.18.37 · arch/arm64/kernel/stacktrace.c · kunwind_next_frame_record 216–245행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
FP와 LR
FP는 프레임 기록 위치를 찾는 기준이고 LR은 호출자의 복귀 주소입니다. 둘은 같은 주소가 아닙니다.
스택 범위 검사
잘못된 FP가 임의 메모리를 가리켜도 추적기가 그것을 무작정 읽지 않도록 합니다.
READ_ONCE
컴파일러가 동일 필드 접근을 임의로 합치거나 다시 읽는 일을 제어합니다. 이것만으로 실행 중 task 전체의 스냅샷이 생기지는 않습니다.
처음 읽을 때
프레임 한 개 안에 이전 FP와 LR 두 값이 있다는 그림부터 그려 보십시오.
더 깊이 살펴볼 때
스택 소비 표시가 순환·역방향 추적을 막는 역할과 예외 스택 메타데이터의 전환을 검토하십시오.
그림으로 보는 변화

1. 읽을 위치 검증
FP 정렬과 프레임 전체가 유효한 스택 범위에 있는지 확인합니다.
화살표는 검증을 통과한 경우에만 다음 읽기로 이어집니다.
2. 다음 주소 확보
record의 FP와 LR을 한 번씩 읽습니다.
메모리에서 다음 추적 상태로 값이 이동합니다.
3. 추적 상태 전진
사용한 스택 범위를 소비 처리하고 새 FP·PC를 기록합니다.
화살표는 호출자가 있는 이전 프레임 쪽 이동이며 CPU 실행 방향이 아닙니다.
kunwind_next_frame_record를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
static __always_inline int
kunwind_next_frame_record(struct kunwind_state *state)
{
unsigned long fp = state->common.fp;
struct frame_record *record;
struct stack_info *info;
unsigned long new_fp, new_pc;
if (fp & 0x7)
return -EINVAL;
info = unwind_find_stack(&state->common, fp, sizeof(*record));
if (!info)
return -EINVAL;
record = (struct frame_record *)fp;
new_fp = READ_ONCE(record->fp);
new_pc = READ_ONCE(record->lr);
if (!new_fp && !new_pc)
return kunwind_next_frame_record_meta(state);
unwind_consume_stack(&state->common, info, fp, sizeof(*record));
state->common.fp = new_fp;
state->common.pc = new_pc;
state->source = KUNWIND_SOURCE_FRAME;
return 0;
}static __always_inline int호출 지점에 인라인될 수 있는 내부 helper이며 성공 또는 오류를 int로 반환합니다.
kunwind_next_frame_record(struct kunwind_state *state)현재 추적 위치와 스택 사용 상태가 들어 있는 state를 받아 다음 프레임으로 진행합니다.
unsigned long fp = state->common.fp;지금 프레임을 찾을 FP 값을 지역 변수로 꺼냅니다. 이 주소를 바로 역참조하지 않고 먼저 검사합니다.
struct frame_record *record;검증 후 사용할 frame_record 포인터를 선언합니다.
struct stack_info *info;이 주소가 들어 있는 유효한 스택 영역 설명을 받을 포인터입니다.
unsigned long new_fp, new_pc;다음 FP와 PC를 따로 보관합니다. 검증을 마치기 전에 현재 추적 상태를 덮어쓰지 않도록 합니다.
if (fp & 0x7)FP의 하위 3비트가 0인지 확인하여 이 레코드 접근의 8바이트 정렬 조건을 검사합니다.
return -EINVAL;정렬이 잘못된 주소이면 추적을 계속하지 않고 잘못된 입력 오류를 반환합니다.
info = unwind_find_stack(&state->common, fp, sizeof(*record));FP부터 frame_record 전체 크기까지 들어가는 유효한 스택 영역을 찾습니다. 시작 주소만 범위 안이라고 충분하지 않습니다.
if (!info)해당 범위를 담는 스택을 찾지 못했는지 확인합니다.
return -EINVAL;스택 밖의 임의 메모리를 프레임으로 읽지 않도록 오류로 끝냅니다.
record = (struct frame_record *)fp;검사가 끝난 FP를 프레임 기록 포인터로 해석합니다.
new_fp = READ_ONCE(record->fp);레코드에 저장된 이전 FP를 한 번 읽습니다. READ_ONCE는 컴파일러가 접근을 임의로 합치거나 다시 생성하는 일을 제한합니다.
new_pc = READ_ONCE(record->lr);저장된 LR을 다음 PC 값으로 읽습니다. FP와 LR은 각각 연결 정보와 복귀 위치입니다.
if (!new_fp && !new_pc)두 값이 모두 0인 특수 레코드인지 확인합니다.
return kunwind_next_frame_record_meta(state);그 경우 일반 연결을 따르지 않고 별도 프레임 메타데이터 경로로 넘깁니다.
unwind_consume_stack(&state->common, info, fp, sizeof(*record));이미 사용한 프레임의 스택 범위를 소비 처리하여 이후 걷기가 같은 영역을 부적절하게 다시 사용하지 않도록 합니다.
state->common.fp = new_fp;다음 단계가 사용할 FP를 갱신합니다.
state->common.pc = new_pc;이번에 찾은 호출자의 복귀 위치를 추적 PC로 기록합니다.
state->source = KUNWIND_SOURCE_FRAME;이 결과가 frame record에서 얻어진 것임을 표시합니다. 다른 메타데이터 출처와 구분하는 정보입니다.
return 0;다음 프레임으로 한 단계 진행했음을 성공으로 반환합니다.
함께 생각해 볼 질문
스택에서 큰 숫자를 찾으면 복귀 주소입니까?
아닙니다. ABI·메타데이터·범위 검사를 통해 프레임임을 확인해야 합니다.
정렬 검사만 통과하면 안전합니까?
아닙니다. 프레임 전체가 접근 가능한 스택 범위에 있어야 합니다.
FP와 LR이 모두 0이면 무조건 오류입니까?
이 함수는 별도 frame-record 메타데이터 처리로 넘깁니다.
출처와 읽은 범위
Linux stable v6.18.37 · arch/arm64/kernel/stacktrace.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
