Commit Fixes: b9f6fbb3b2c29736 ("perf arm64: Inject missing frames when using 'perf record --call-graph=fp'") intended to add a 'best effort' DWARF unwind that improved the frame pointer stack in most scenarios. It's expected that the unwind will fail sometimes, but this shouldn't be reported as an error. It only works when the return address can be determined from the contents of the link register alone. Fix the error shown when the unwinder requires extra registers by adding a new flag that suppresses error messages. This flag is not set in the normal --call-graph=dwarf unwind mode so that behavior is not changed.
Unified · Split
| @@ -122,7 +122,7 @@ NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__thread(struct thread *thr · 121 unmodified lines | ||
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | err = unwind__get_entries(unwind_entry, &cnt, thread, |
| 125 | − &sample, MAX_STACK); | |
| 125 | + &sample, MAX_STACK, false); | |
| 126 | 126 | if (err) |
| 127 | 127 | pr_debug("unwind failed\n"); |
| 128 | 128 | else if (cnt != MAX_STACK) { |
| @@ -53,7 +53,7 @@ u64 get_leaf_frame_caller_aarch64(struct perf_sample *sample, struct thread *thr · 52 unmodified lines | ||
| 53 | 53 | sample->user_regs.cache_regs[PERF_REG_ARM64_SP] = 0; |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | − ret = unwind__get_entries(add_entry, &entries, thread, sample, 2); | |
| 56 | + ret = unwind__get_entries(add_entry, &entries, thread, sample, 2, true); | |
| 57 | 57 | sample->user_regs = old_regs; |
| 58 | 58 | |
| 59 | 59 | if (ret || entries.length != 2) |
| @@ -2987,7 +2987,7 @@ static int thread__resolve_callchain_unwind(struct thread *thread, · 2986 unmodified lines | ||
| 2987 | 2987 | return 0; |
| 2988 | 2988 | |
| 2989 | 2989 | return unwind__get_entries(unwind_entry, cursor, |
| 2990 | − thread, sample, max_stack); | |
| 2990 | + thread, sample, max_stack, false); | |
| 2991 | 2991 | } |
| 2992 | 2992 | |
| 2993 | 2993 | int thread__resolve_callchain(struct thread *thread, |
| @@ -200,7 +200,8 @@ frame_callback(Dwfl_Frame *state, void *arg) · 199 unmodified lines | ||
| 200 | 200 | bool isactivation; |
| 201 | 201 | |
| 202 | 202 | if (!dwfl_frame_pc(state, &pc, NULL)) { |
| 203 | − pr_err("%s", dwfl_errmsg(-1)); | |
| 203 | + if (!ui->best_effort) | |
| 204 | + pr_err("%s", dwfl_errmsg(-1)); | |
| 204 | 205 | return DWARF_CB_ABORT; |
| 205 | 206 | } |
| 206 | 207 | |
| @@ -208,7 +209,8 @@ frame_callback(Dwfl_Frame *state, void *arg) · 1 unmodified line | ||
| 208 | 209 | report_module(pc, ui); |
| 209 | 210 | |
| 210 | 211 | if (!dwfl_frame_pc(state, &pc, &isactivation)) { |
| 211 | − pr_err("%s", dwfl_errmsg(-1)); | |
| 212 | + if (!ui->best_effort) | |
| 213 | + pr_err("%s", dwfl_errmsg(-1)); | |
| 212 | 214 | return DWARF_CB_ABORT; |
| 213 | 215 | } |
| 214 | 216 | |
| @@ -222,7 +224,8 @@ frame_callback(Dwfl_Frame *state, void *arg) · 7 unmodified lines | ||
| 222 | 224 | int unwind__get_entries(unwind_entry_cb_t cb, void *arg, |
| 223 | 225 | struct thread *thread, |
| 224 | 226 | struct perf_sample *data, |
| 225 | − int max_stack) | |
| 227 | + int max_stack, | |
| 228 | + bool best_effort) | |
| 226 | 229 | { |
| 227 | 230 | struct unwind_info *ui, ui_buf = { |
| 228 | 231 | .sample = data, |
| @@ -231,6 +234,7 @@ int unwind__get_entries(unwind_entry_cb_t cb, void *arg, · 2 unmodified lines | ||
| 231 | 234 | .cb = cb, |
| 232 | 235 | .arg = arg, |
| 233 | 236 | .max_stack = max_stack, |
| 237 | + .best_effort = best_effort | |
| 234 | 238 | }; |
| 235 | 239 | Dwarf_Word ip; |
| 236 | 240 | int err = -EINVAL, i; |
| @@ -20,6 +20,7 @@ struct unwind_info { · 19 unmodified lines | ||
| 20 | 20 | void *arg; |
| 21 | 21 | int max_stack; |
| 22 | 22 | int idx; |
| 23 | + bool best_effort; | |
| 23 | 24 | struct unwind_entry entries[]; |
| 24 | 25 | }; |
| 25 | 26 | |
| @@ -96,6 +96,7 @@ struct unwind_info { · 95 unmodified lines | ||
| 96 | 96 | struct perf_sample *sample; |
| 97 | 97 | struct machine *machine; |
| 98 | 98 | struct thread *thread; |
| 99 | + bool best_effort; | |
| 99 | 100 | }; |
| 100 | 101 | |
| 101 | 102 | #define dw_read(ptr, type, end) ({ \ |
| @@ -553,7 +554,8 @@ static int access_reg(unw_addr_space_t __maybe_unused as, · 451 unmodified lines | ||
| 553 | 554 | |
| 554 | 555 | ret = perf_reg_value(&val, &ui->sample->user_regs, id); |
| 555 | 556 | if (ret) { |
| 556 | − pr_err("unwind: can't read reg %d\n", regnum); | |
| 557 | + if (!ui->best_effort) | |
| 558 | + pr_err("unwind: can't read reg %d\n", regnum); | |
| 557 | 559 | return ret; |
| 558 | 560 | } |
| 559 | 561 | |
| @@ -666,7 +668,7 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb, · 106 unmodified lines | ||
| 666 | 668 | return -1; |
| 667 | 669 | |
| 668 | 670 | ret = unw_init_remote(&c, addr_space, ui); |
| 669 | − if (ret) | |
| 671 | + if (ret && !ui->best_effort) | |
| 670 | 672 | display_error(ret); |
| 671 | 673 | |
| 672 | 674 | while (!ret && (unw_step(&c) > 0) && i < max_stack) { |
| @@ -704,12 +706,14 @@ static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb, · 31 unmodified lines | ||
| 704 | 706 | |
| 705 | 707 | static int _unwind__get_entries(unwind_entry_cb_t cb, void *arg, |
| 706 | 708 | struct thread *thread, |
| 707 | − struct perf_sample *data, int max_stack) | |
| 709 | + struct perf_sample *data, int max_stack, | |
| 710 | + bool best_effort) | |
| 708 | 711 | { |
| 709 | 712 | struct unwind_info ui = { |
| 710 | 713 | .sample = data, |
| 711 | 714 | .thread = thread, |
| 712 | 715 | .machine = thread->maps->machine, |
| 716 | + .best_effort = best_effort | |
| 713 | 717 | }; |
| 714 | 718 | |
| 715 | 719 | if (!data->user_regs.regs) |
| @@ -80,9 +80,11 @@ void unwind__finish_access(struct maps *maps) · 79 unmodified lines | ||
| 80 | 80 | |
| 81 | 81 | int unwind__get_entries(unwind_entry_cb_t cb, void *arg, |
| 82 | 82 | struct thread *thread, |
| 83 | − struct perf_sample *data, int max_stack) | |
| 83 | + struct perf_sample *data, int max_stack, | |
| 84 | + bool best_effort) | |
| 84 | 85 | { |
| 85 | 86 | if (thread->maps->unwind_libunwind_ops) |
| 86 | − return thread->maps->unwind_libunwind_ops->get_entries(cb, arg, thread, data, max_stack); | |
| 87 | + return thread->maps->unwind_libunwind_ops->get_entries(cb, arg, thread, data, | |
| 88 | + max_stack, best_effort); | |
| 87 | 89 | return 0; |
| 88 | 90 | } |
| @@ -23,13 +23,19 @@ struct unwind_libunwind_ops { · 22 unmodified lines | ||
| 23 | 23 | void (*finish_access)(struct maps *maps); |
| 24 | 24 | int (*get_entries)(unwind_entry_cb_t cb, void *arg, |
| 25 | 25 | struct thread *thread, |
| 26 | − struct perf_sample *data, int max_stack); | |
| 26 | + struct perf_sample *data, int max_stack, bool best_effort); | |
| 27 | 27 | }; |
| 28 | 28 | |
| 29 | 29 | #ifdef HAVE_DWARF_UNWIND_SUPPORT |
| 30 | +/* | |
| 31 | + * When best_effort is set, don't report errors and fail silently. This could | |
| 32 | + * be expanded in the future to be more permissive about things other than | |
| 33 | + * error messages. | |
| 34 | + */ | |
| 30 | 35 | int unwind__get_entries(unwind_entry_cb_t cb, void *arg, |
| 31 | 36 | struct thread *thread, |
| 32 | − struct perf_sample *data, int max_stack); | |
| 37 | + struct perf_sample *data, int max_stack, | |
| 38 | + bool best_effort); | |
| 33 | 39 | /* libunwind specific */ |
| 34 | 40 | #ifdef HAVE_LIBUNWIND_SUPPORT |
| 35 | 41 | #ifndef LIBUNWIND__ARCH_REG_ID |
| @@ -65,7 +71,8 @@ unwind__get_entries(unwind_entry_cb_t cb __maybe_unused, · 29 unmodified lines | ||
| 65 | 71 | void *arg __maybe_unused, |
| 66 | 72 | struct thread *thread __maybe_unused, |
| 67 | 73 | struct perf_sample *data __maybe_unused, |
| 68 | − int max_stack __maybe_unused) | |
| 74 | + int max_stack __maybe_unused, | |
| 75 | + bool best_effort __maybe_unused) | |
| 69 | 76 | { |
| 70 | 77 | return 0; |
| 71 | 78 | } |