01 · QUESTION
무엇을 확인할 것인가
frame pointer가 없거나 interrupt가 별도 stack에서 들어왔을 때 kernel은 어떤 근거로 다음 stack frame과 return address를 신뢰하는가?
stack walker는 현재 PC/SP/FP와 task stack 경계를 받아 callback으로 frame을 전달한다. interrupt, exception, scheduler와 trampoline frame을 인식해 다른 stack segment로 넘어가며 invalid address와 loop를 차단한다.
live current-task unwind, stopped remote task, NMI/IRQ stack과 user stack을 구분한다. tracing instrumentation 자체가 stack을 바꾸거나 return address를 수정할 수 있으므로 ftrace/kretprobe metadata를 적용한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | x29 frame record와 LR/PAC/SCS 처리 | 깨진 x29 chain, PAC 미제거 또는 IRQ stack 경계 누락은 잘못된 symbol과 out-of-bounds stack read를 만든다. | FP/SP 정렬, task/IRQ/overflow stack range, saved LR, PAC mask, SCS pointer와 unwind consumer를 확인한다. |
| x86-64 | ORC unwind table과 exception/IRQ stack metadata | 손상되거나 다른 build의 ORC table, noinstr 규칙 위반 또는 stack type 오판은 unwind stop과 잘못된 call trace를 만든다. | IP의 ORC entry, SP/BP base와 offset, stack_info type, pt_regs 위치, module unwind table과 objtool warning을 본다. |
| RISC-V | s0 frame pointer chain 또는 compiler unwind 제한 | frame-pointer omission, leaf function의 RA 보존 방식 또는 손상된 s0가 call chain 단절과 잘못된 address를 만든다. | s0/fp, sp, ra, task/IRQ stack range, pt_regs epc와 CONFIG_FRAME_POINTER/ftrace 설정을 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- x29 frame record와 LR/PAC/SCS 처리
- state
- AAPCS frame record의 previous FP와 saved LR을 따라간다. exception entry frame, fgraph return trampoline, pointer authentication signed LR와 shadow call stack 구성을 고려해 실제 return PC를 복원한다.
- checkpoint
- FP/SP 정렬, task/IRQ/overflow stack range, saved LR, PAC mask, SCS pointer와 unwind consumer를 확인한다.
x86-64
- mechanism
- ORC unwind table과 exception/IRQ stack metadata
- state
- objtool이 instruction별 SP/BP 복원 규칙을 ORC table로 만든다. walker는 current IP에 해당하는 ORC entry를 찾아 다음 register state를 계산하고 pt_regs, call, regs, signal frame 유형을 구분한다.
- checkpoint
- IP의 ORC entry, SP/BP base와 offset, stack_info type, pt_regs 위치, module unwind table과 objtool warning을 본다.
RISC-V
- mechanism
- s0 frame pointer chain 또는 compiler unwind 제한
- state
- frame pointer build에서는 s0가 가리키는 frame record에서 previous FP와 RA를 읽는다. exception pt_regs와 task stack 경계를 인식하고 instruction pointer를 callback에 넘긴다.
- checkpoint
- s0/fp, sp, ra, task/IRQ stack range, pt_regs epc와 CONFIG_FRAME_POINTER/ftrace 설정을 확인한다.
04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
x29 frame record와 LR/PAC/SCS 처리
AAPCS frame record의 previous FP와 saved LR을 따라간다. exception entry frame, fgraph return trampoline, pointer authentication signed LR와 shadow call stack 구성을 고려해 실제 return PC를 복원한다.
원본 코드: arch/arm64/kernel/stacktrace.c:373-435
373
374static __always_inline bool
375arch_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
376{
377 struct kunwind_consume_entry_data *data = cookie;
378 return data->consume_entry(data->cookie, state->common.pc);
379}
380
381noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,
382 void *cookie, struct task_struct *task,
383 struct pt_regs *regs)
384{
385 struct kunwind_consume_entry_data data = {
386 .consume_entry = consume_entry,
387 .cookie = cookie,
388 };
389
390 kunwind_stack_walk(arch_kunwind_consume_entry, &data, task, regs);
391}
392
393static __always_inline bool
394arch_reliable_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
395{
396 /*
397 * At an exception boundary we can reliably consume the saved PC. We do
398 * not know whether the LR was live when the exception was taken, and
399 * so we cannot perform the next unwind step reliably.
400 *
401 * All that matters is whether the *entire* unwind is reliable, so give
402 * up as soon as we hit an exception boundary.
403 */
404 if (state->source == KUNWIND_SOURCE_REGS_PC)
405 return false;
406
407 return arch_kunwind_consume_entry(state, cookie);
408}
409
410noinline noinstr int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
411 void *cookie,
412 struct task_struct *task)
413{
414 struct kunwind_consume_entry_data data = {
415 .consume_entry = consume_entry,
416 .cookie = cookie,
417 };
418
419 return kunwind_stack_walk(arch_reliable_kunwind_consume_entry, &data,
420 task, NULL);
421}
422
423struct bpf_unwind_consume_entry_data {
424 bool (*consume_entry)(void *cookie, u64 ip, u64 sp, u64 fp);
425 void *cookie;
426};
427
428static bool
429arch_bpf_unwind_consume_entry(const struct kunwind_state *state, void *cookie)
430{
431 struct bpf_unwind_consume_entry_data *data = cookie;
432
433 return data->consume_entry(data->cookie, state->common.pc, 0,
434 state->common.fp);
435}라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 63개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline bool선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
arch_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct kunwind_consume_entry_data *data = cookie;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
return data->consume_entry(data->cookie, state->common.pc);이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
void *cookie, struct task_struct *task,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct pt_regs *regs)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct kunwind_consume_entry_data data = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.consume_entry = consume_entry,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.cookie = cookie,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
kunwind_stack_walk(arch_kunwind_consume_entry, &data, task, regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static __always_inline bool선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
arch_reliable_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* At an exception boundary we can reliably consume the saved PC. We doLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* not know whether the LR was live when the exception was taken, andLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* so we cannot perform the next unwind step reliably.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* All that matters is whether the *entire* unwind is reliable, so giveLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* up as soon as we hit an exception boundary.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (state->source == KUNWIND_SOURCE_REGS_PC)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return arch_kunwind_consume_entry(state, cookie);이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
noinline noinstr int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
void *cookie,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct task_struct *task)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct kunwind_consume_entry_data data = {계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.consume_entry = consume_entry,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.cookie = cookie,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return kunwind_stack_walk(arch_reliable_kunwind_consume_entry, &data,이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
task, NULL);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
struct bpf_unwind_consume_entry_data {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
bool (*consume_entry)(void *cookie, u64 ip, u64 sp, u64 fp);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
void *cookie;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
};C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static bool선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
arch_bpf_unwind_consume_entry(const struct kunwind_state *state, void *cookie)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct bpf_unwind_consume_entry_data *data = cookie;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return data->consume_entry(data->cookie, state->common.pc, 0,이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
state->common.fp);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
x86-64 · Linux 6.18.37
ORC unwind table과 exception/IRQ stack metadata
objtool이 instruction별 SP/BP 복원 규칙을 ORC table로 만든다. walker는 current IP에 해당하는 ORC entry를 찾아 다음 register state를 계산하고 pt_regs, call, regs, signal frame 유형을 구분한다.
원본 코드: arch/x86/kernel/unwind_orc.c:460-558
460 if (state->prev_regs) {
461 *val = READ_ONCE_NOCHECK(((unsigned long *)state->prev_regs)[reg]);
462 return true;
463 }
464
465 return false;
466}
467
468bool unwind_next_frame(struct unwind_state *state)
469{
470 unsigned long ip_p, sp, tmp, orig_ip = state->ip, prev_sp = state->sp;
471 enum stack_type prev_type = state->stack_info.type;
472 struct orc_entry *orc;
473 bool indirect = false;
474
475 if (unwind_done(state))
476 return false;
477
478 /* Don't let modules unload while we're reading their ORC data. */
479 guard(rcu)();
480
481 /* End-of-stack check for user tasks: */
482 if (state->regs && user_mode(state->regs))
483 goto the_end;
484
485 /*
486 * Find the orc_entry associated with the text address.
487 *
488 * For a call frame (as opposed to a signal frame), state->ip points to
489 * the instruction after the call. That instruction's stack layout
490 * could be different from the call instruction's layout, for example
491 * if the call was to a noreturn function. So get the ORC data for the
492 * call instruction itself.
493 */
494 orc = orc_find(state->signal ? state->ip : state->ip - 1);
495 if (!orc) {
496 /*
497 * As a fallback, try to assume this code uses a frame pointer.
498 * This is useful for generated code, like BPF, which ORC
499 * doesn't know about. This is just a guess, so the rest of
500 * the unwind is no longer considered reliable.
501 */
502 orc = &orc_fp_entry;
503 state->error = true;
504 } else {
505 if (orc->type == ORC_TYPE_UNDEFINED)
506 goto err;
507
508 if (orc->type == ORC_TYPE_END_OF_STACK)
509 goto the_end;
510 }
511
512 state->signal = orc->signal;
513
514 /* Find the previous frame's stack: */
515 switch (orc->sp_reg) {
516 case ORC_REG_SP:
517 sp = state->sp + orc->sp_offset;
518 break;
519
520 case ORC_REG_BP:
521 sp = state->bp + orc->sp_offset;
522 break;
523
524 case ORC_REG_SP_INDIRECT:
525 sp = state->sp;
526 indirect = true;
527 break;
528
529 case ORC_REG_BP_INDIRECT:
530 sp = state->bp + orc->sp_offset;
531 indirect = true;
532 break;
533
534 case ORC_REG_R10:
535 if (!get_reg(state, offsetof(struct pt_regs, r10), &sp)) {
536 orc_warn_current("missing R10 value at %pB\n",
537 (void *)state->ip);
538 goto err;
539 }
540 break;
541
542 case ORC_REG_R13:
543 if (!get_reg(state, offsetof(struct pt_regs, r13), &sp)) {
544 orc_warn_current("missing R13 value at %pB\n",
545 (void *)state->ip);
546 goto err;
547 }
548 break;
549
550 case ORC_REG_DI:
551 if (!get_reg(state, offsetof(struct pt_regs, di), &sp)) {
552 orc_warn_current("missing RDI value at %pB\n",
553 (void *)state->ip);
554 goto err;
555 }
556 break;
557
558 case ORC_REG_DX:라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 99개 줄에 각각 설명을 붙였습니다.
if (state->prev_regs) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
*val = READ_ONCE_NOCHECK(((unsigned long *)state->prev_regs)[reg]);Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
return true;검증된 다음 frame을 consumer가 읽을 수 있음을 나타낸다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return false;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
bool unwind_next_frame(struct unwind_state *state)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long ip_p, sp, tmp, orig_ip = state->ip, prev_sp = state->sp;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
enum stack_type prev_type = state->stack_info.type;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
struct orc_entry *orc;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
bool indirect = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (unwind_done(state))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Don't let modules unload while we're reading their ORC data. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
guard(rcu)();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* End-of-stack check for user tasks: */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (state->regs && user_mode(state->regs))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
goto the_end;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Find the orc_entry associated with the text address.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* For a call frame (as opposed to a signal frame), state->ip points toLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the instruction after the call. That instruction's stack layoutLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* could be different from the call instruction's layout, for exampleLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* if the call was to a noreturn function. So get the ORC data for theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* call instruction itself.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
orc = orc_find(state->signal ? state->ip : state->ip - 1);현재 instruction이 call 이후인지 signal frame인지 반영해 대응하는 ORC rule을 찾는다.
if (!orc) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* As a fallback, try to assume this code uses a frame pointer.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This is useful for generated code, like BPF, which ORCLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* doesn't know about. This is just a guess, so the rest ofLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the unwind is no longer considered reliable.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
orc = &orc_fp_entry;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
state->error = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
} else {이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
if (orc->type == ORC_TYPE_UNDEFINED)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
goto err;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (orc->type == ORC_TYPE_END_OF_STACK)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
goto the_end;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
state->signal = orc->signal;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Find the previous frame's stack: */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
switch (orc->sp_reg) {ORC가 지정한 base register와 offset으로 caller SP를 계산한다.
case ORC_REG_SP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
sp = state->sp + orc->sp_offset;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_BP:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
sp = state->bp + orc->sp_offset;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_SP_INDIRECT:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
sp = state->sp;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
indirect = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_BP_INDIRECT:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
sp = state->bp + orc->sp_offset;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
indirect = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_R10:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
if (!get_reg(state, offsetof(struct pt_regs, r10), &sp)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
orc_warn_current("missing R10 value at %pB\n",이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(void *)state->ip);이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
goto err;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_R13:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
if (!get_reg(state, offsetof(struct pt_regs, r13), &sp)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
orc_warn_current("missing R13 value at %pB\n",이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(void *)state->ip);이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
goto err;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_DI:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
if (!get_reg(state, offsetof(struct pt_regs, di), &sp)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
orc_warn_current("missing RDI value at %pB\n",이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(void *)state->ip);이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
goto err;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 x86-64 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
case ORC_REG_DX:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
RISC-V · Linux 6.18.37
s0 frame pointer chain 또는 compiler unwind 제한
frame pointer build에서는 s0가 가리키는 frame record에서 previous FP와 RA를 읽는다. exception pt_regs와 task stack 경계를 인식하고 instruction pointer를 callback에 넘긴다.
원본 코드: arch/riscv/kernel/stacktrace.c:171-227
171
172 if (!try_get_task_stack(task))
173 return 0;
174 walk_stackframe(task, NULL, save_wchan, &pc);
175 put_task_stack(task);
176 return pc;
177}
178
179noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
180 struct task_struct *task, struct pt_regs *regs)
181{
182 walk_stackframe(task, regs, consume_entry, cookie);
183}
184
185/*
186 * Get the return address for a single stackframe and return a pointer to the
187 * next frame tail.
188 */
189static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,
190 void *cookie, unsigned long fp,
191 unsigned long reg_ra)
192{
193 struct stackframe buftail;
194 unsigned long ra = 0;
195 unsigned long __user *user_frame_tail =
196 (unsigned long __user *)(fp - sizeof(struct stackframe));
197
198 /* Check accessibility of one struct frame_tail beyond */
199 if (!access_ok(user_frame_tail, sizeof(buftail)))
200 return 0;
201 if (__copy_from_user_inatomic(&buftail, user_frame_tail,
202 sizeof(buftail)))
203 return 0;
204
205 ra = reg_ra ? : buftail.ra;
206
207 fp = buftail.fp;
208 if (!ra || !consume_entry(cookie, ra))
209 return 0;
210
211 return fp;
212}
213
214void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
215 const struct pt_regs *regs)
216{
217 unsigned long fp = 0;
218
219 fp = regs->s0;
220 if (!consume_entry(cookie, regs->epc))
221 return;
222
223 fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);
224 while (fp && !(fp & 0x7))
225 fp = unwind_user_frame(consume_entry, cookie, fp, 0);
226}
227 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 57개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!try_get_task_stack(task))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return 0;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
walk_stackframe(task, NULL, save_wchan, &pc);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
put_task_stack(task);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return pc;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct task_struct *task, struct pt_regs *regs)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
walk_stackframe(task, regs, consume_entry, cookie);초기 pt_regs 또는 task context에서 frame-pointer chain을 순회하고 callback으로 PC를 전달한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Get the return address for a single stackframe and return a pointer to theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* next frame tail.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
static unsigned long unwind_user_frame(stack_trace_consume_fn consume_entry,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
void *cookie, unsigned long fp,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned long reg_ra)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct stackframe buftail;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned long ra = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
unsigned long __user *user_frame_tail =이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(unsigned long __user *)(fp - sizeof(struct stackframe));helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Check accessibility of one struct frame_tail beyond */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!access_ok(user_frame_tail, sizeof(buftail)))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return 0;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
if (__copy_from_user_inatomic(&buftail, user_frame_tail,이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
sizeof(buftail)))이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
return 0;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ra = reg_ra ? : buftail.ra;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
fp = buftail.fp;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!ra || !consume_entry(cookie, ra))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return 0;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return fp;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const struct pt_regs *regs)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Stack unwind, function tracing과 return address 보호의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long fp = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
fp = regs->s0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!consume_entry(cookie, regs->epc))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 Stack unwind, function tracing과 return address 보호 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
fp = unwind_user_frame(consume_entry, cookie, fp, regs->ra);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
while (fp && !(fp & 0x7))range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
fp = unwind_user_frame(consume_entry, cookie, fp, 0);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 Stack unwind, function tracing과 return address 보호 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
interrupt가 중간에 끼어든 call chain
worker -> submit_io -> wait 실행 중 timer interrupt가 들어와 hrtimer_interrupt -> scheduler_tick을 실행했다고 가정한다.
- top현재 PC는 scheduler_tick 내부이고 첫 frame들은 IRQ handler stack에 있다.
- entry framewalker가 exception/interrupt pt_regs를 만나면 saved PC=wait와 interrupted SP를 읽는다.
- stack switch현재 IRQ stack range를 끝내고 task kernel stack range로 검사 기준을 바꾼다.
- callerwait, submit_io, worker frame을 이어서 출력하되 return PC가 kernel text/module text인지 확인한다.
결론단순히 stack word에서 text address를 찾는 것이 아니라 metadata와 stack 경계로 각 return address의 출처를 증명한다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
stack walker는 현재 PC/SP/FP와 task stack 경계를 받아 callback으로 frame을 전달한다. interrupt, exception, scheduler와 trampoline frame을 인식해 다른 stack segment로 넘어가며 invalid address와 loop를 차단한다.
live current-task unwind, stopped remote task, NMI/IRQ stack과 user stack을 구분한다. tracing instrumentation 자체가 stack을 바꾸거나 return address를 수정할 수 있으므로 ftrace/kretprobe metadata를 적용한다.
arm64: x29 frame record와 LR/PAC/SCS 처리
AAPCS frame record의 previous FP와 saved LR을 따라간다. exception entry frame, fgraph return trampoline, pointer authentication signed LR와 shadow call stack 구성을 고려해 실제 return PC를 복원한다.
stack boundary 검사 뒤 LR의 PAC를 strip하고 kernel text 범위인지 확인한다. current task가 아니면 안정적으로 정지된 상태여야 한다. 디버깅할 때는 FP/SP 정렬, task/IRQ/overflow stack range, saved LR, PAC mask, SCS pointer와 unwind consumer를 확인한다.
x86-64: ORC unwind table과 exception/IRQ stack metadata
objtool이 instruction별 SP/BP 복원 규칙을 ORC table로 만든다. walker는 current IP에 해당하는 ORC entry를 찾아 다음 register state를 계산하고 pt_regs, call, regs, signal frame 유형을 구분한다.
binary와 ORC metadata가 정확히 일치해야 하며 runtime code patching과 module load는 해당 table을 함께 등록한다. 디버깅할 때는 IP의 ORC entry, SP/BP base와 offset, stack_info type, pt_regs 위치, module unwind table과 objtool warning을 본다.
RISC-V: s0 frame pointer chain 또는 compiler unwind 제한
frame pointer build에서는 s0가 가리키는 frame record에서 previous FP와 RA를 읽는다. exception pt_regs와 task stack 경계를 인식하고 instruction pointer를 callback에 넘긴다.
compressed instruction과 ftrace return-address patching을 고려하고 stack range를 벗어나면 즉시 중단한다. 디버깅할 때는 s0/fp, sp, ra, task/IRQ stack range, pt_regs epc와 CONFIG_FRAME_POINTER/ftrace 설정을 확인한다.
객체 수명과 소유권을 먼저 고정한다
task stack과 unwind metadata는 walk가 끝날 때까지 유효해야 한다. remote running task를 정지하지 않고 walk하면 SP와 frame contents가 동시에 바뀌어 일관된 call chain을 보장할 수 없다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
frame-pointer walk는 frame 수에 비례하고 ORC/DWARF-like state lookup은 metadata search가 더해진다. NMI에서 긴 unwind와 symbol lookup은 watchdog latency를 악화시킬 수 있다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | 깨진 x29 chain, PAC 미제거 또는 IRQ stack 경계 누락은 잘못된 symbol과 out-of-bounds stack read를 만든다. | FP/SP 정렬, task/IRQ/overflow stack range, saved LR, PAC mask, SCS pointer와 unwind consumer를 확인한다. |
| x86-64 | 손상되거나 다른 build의 ORC table, noinstr 규칙 위반 또는 stack type 오판은 unwind stop과 잘못된 call trace를 만든다. | IP의 ORC entry, SP/BP base와 offset, stack_info type, pt_regs 위치, module unwind table과 objtool warning을 본다. |
| RISC-V | frame-pointer omission, leaf function의 RA 보존 방식 또는 손상된 s0가 call chain 단절과 잘못된 address를 만든다. | s0/fp, sp, ra, task/IRQ stack range, pt_regs epc와 CONFIG_FRAME_POINTER/ftrace 설정을 확인한다. |
08 · LAB
재현과 계측 절차
- 같은 call path를 frame-pointer/ORC 가능한 build에서 비교하고 interrupt stack 전환 frame을 표시한다.
- module load/unload와 동시에 stack sampling해 module text/unwind metadata lifetime 보호를 확인한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/stacktrace.c:373-435
- x86-64arch/x86/kernel/unwind_orc.c:460-558
- RISC-Varch/riscv/kernel/stacktrace.c:171-227
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.