我不是無所不知, 只是剛好知道而已。
「
實做 c backtrace 」介紹了怎麼實作 backtrace, 下一個議題是: 如果從 signal handle 執行 backtrace 的動作, 能正常追到源頭呼叫的人嗎?
應該可以吧, 你這麼認為, 覺得在 signal handle 執行 backtrace 和在一般 function 執行 backtrace 不應該有什麼不同才是。
在這之前可以先想想看, 你寫的程式要怎麼辦到在 signal 來時, 切斷正在執行的部份, 然後轉到 signal handle 執行, 最後還可以接回原本的地方執行。
先來看看當 signal handle 發動的時候, 整個處理流程是怎麼樣? 設計了一個小程式來做這樣的實驗, 按照 t1.c 的程式, 有 2 個 thread, 一個停在 L255 while, 一個停在 L384 while, 這時候 kill pid 會得到怎麼樣的 backtrace 呢?
list 1
main thread pid: 27873
main thread tid: 27873
123 thread pid: 27873
123 thread tid: 27874
list 1 列出 2 個 thread 的 pid, tid。一個是 main thread, 一個是用 pthread_create() 建立的 thread。gettid 用法在 t1.c L16 ~ L20。
list 2 by glibc backtrace()
如果 kill 27873 會得到 main thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_223 -> fun_22 -> main -> __libc_start_main -> _start
如果 kill 27874 會得到 thread_1 thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_123 -> fun_12 -> fun_1 -> start_thread -> __clone
list 2. 使用 glibc backtrace() 得到的結果。
如果是由我自己藉由 rpb 追朔 stack 就不是這樣, 少了一部分的資訊。list3 少了 list 2 紅色部份,
fun_223, fun_123 。
list 3 by stack rbp
如果 kill 27873 會得到 main thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_22 -> main -> __libc_start_main
如果 kill 27874 會得到 thread_1 thread 的 backtrace
print_backtrace -> sig_handler -> __restore_rt -> fun_12 -> fun_1 -> start_thread
對於這樣的結果覺得很不爽, 為什麼從 stack frame 無法追朔完整, 而 backtrace() 可以。原因很複雜, 晚點再說。
先來看看為什麼有一個沒出現的 function - __restore_rt。
t1.c
1 #include <signal.h>
2 #include <pthread.h>
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <stdint.h>
9
10 //#define _GNU_SOURCE
11 #include <unistd.h>
12
13 #include <unistd.h>
14 #include <sys/syscall.h>
15
16 #ifndef SYS_gettid
17 #error "SYS_gettid unavailable on this system"
18 #endif
19
20 #define gettid() ((pid_t)syscall(SYS_gettid))
21
22 /* Simple error handling functions */
23
24 #define handle_error_en(en, msg) \
25 do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
26
27 #include <execinfo.h>
28
29 #if 1
30 #define BT_BUF_SIZE 100
31
32 int addr2func(uintptr_t addr)
33 {
34 char cmd[128] = {0};
35 sprintf(cmd, "addr2line -f -e ./t1 %#lx\n", addr);
36 printf("cmd: %s\n", cmd);
37 system(cmd);
38 return 0;
39 }
40
41 #if 1
42 uintptr_t get_rip_value()
43 {
44 //uintptr_t rip_value;
45 //asm volatile("movq $0, %%rax; movq (%%rsp), %%rax" : "=a" (rip_value));
46 //asm("mov %%rip, %0" : "=r" (rip_value));
47 uintptr_t current_address;
48 asm("lea (%%rip), %0" : "=r" (current_address));
49 return current_address;
50 }
51
52 unsigned long get_rbp()
53 {
54 unsigned long rbp_value;
55 asm("movq %%rbp, %0" : "=r" (rbp_value));
56 printf("The value of RBP register is: %#lx\n", rbp_value);
57 return rbp_value;
58 }
59
60 #endif
61
62 void print_backtrace()
63 {
64 void *buffer[BT_BUF_SIZE];
65 char **strings;
66 int nptrs;
67
68 nptrs = backtrace(buffer, BT_BUF_SIZE);
69 printf("backtrace() returned %d addresses\n", nptrs);
70
71 strings = backtrace_symbols(buffer, nptrs);
72 if (strings == NULL) {
73 perror("backtrace_symbols");
74 exit(EXIT_FAILURE);
75 }
76
77 for (int i = 0; i < nptrs; i++)
78 {
79 char cmd[128]={0};
80 printf("nn %s\n", strings[i]);
81 addr2func( (uintptr_t)buffer[i]);
82 #if 0
83 //printf("aa %#x\n", buffer[i]);
84 sprintf(cmd, "/usr/bin/addr2line -f -e ./t123 %p\n", buffer[i]);
85 //printf("cmd: %s\n", cmd);
86 FILE *fs = popen(cmd, "r");
87 if (fs)
88 {
89 char data[128];
90 fread(data, 1, 128, fs);
91 printf("data: %s\n", data);
92 }
93 pclose(fs);
94 #endif
95
96
97 }
98
99 free(strings);
100 }
101 #else
102
103
104 #if 0
105 #define UNW_LOCAL_ONLY
106 #include <libunwind.h>
107 #endif
108
109
110
111 void print_backtrace() {
112 unw_cursor_t cursor;
113 unw_context_t context;
114
115 // 获取当前线程的上下文
116 unw_getcontext(&context);
117 // 初始化游标以便从当前位置开始遍历堆栈帧
118 unw_init_local(&cursor, &context);
119
120 // 遍历堆栈帧
121 while (unw_step(&cursor) > 0) {
122 unw_word_t offset, pc;
123 char sym[256];
124
125 // 获取指令指针位置
126 unw_get_reg(&cursor, UNW_REG_IP, &pc);
127 // 获取调用指令的偏移量
128 unw_get_proc_name(&cursor, sym, sizeof(sym), &offset);
129
130 printf(" 0x%lx: (%s+0x%lx)\n", (long)pc, sym, offset);
131 }
132 }
133
134 #endif
135
136 void sig_handler(int signo)
137 {
138 int level = 0;
139 printf("in sig_handler\n");
140 #if 1
141
142 //printf ("Caller name: %p\n", __builtin_return_address(0));
143 print_backtrace();
144
145 #if 1
146 printf("in sig_handler pid: %d\n", getpid());
147 printf("in sig_handler tid: %d\n", gettid());
148 if (signo == SIGINT)
149 printf("Received SIGINT\n");
150 #endif
151 #endif
152 #if 1
153 {
154 uintptr_t current_address;
155 asm("lea (%%rip), %0" : "=r" (current_address));
156 printf("current_address : %#lx\n", current_address);
157 addr2func(current_address);
158
159 printf("======\n");
160
161 unsigned long rbp_value, previous_rbp;
162 asm("movq %%rbp, %0" : "=r" (rbp_value));
163 printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
164 ++level;
165
166 uintptr_t ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
167 printf("ret_addr : %#lx\n", ret_addr);
168
169 addr2func(ret_addr);
170
171 printf("======\n");
172
173 rbp_value = *(uintptr_t*)(rbp_value);
174 printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
175 ++level;
176
177 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
178 addr2func(ret_addr);
179
180
181 printf("======\n");
182
183 rbp_value = *(uintptr_t*)(rbp_value);
184 printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
185 ++level;
186
187 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
188 addr2func(ret_addr);
189
190 printf("======\n");
191
192 rbp_value = *(uintptr_t*)(rbp_value);
193 printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
194 ++level;
195
196 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
197 addr2func(ret_addr);
198
199 printf("======\n");
200
201 previous_rbp = rbp_value;
202
203 rbp_value = *(uintptr_t*)(rbp_value);
204 printf("%d ## The value of RBP register is: %#lx, previous_rbp: %#lx\n", level, rbp_value, previous_rbp);
205 ++level;
206
207 if (rbp_value > previous_rbp)
208 {
209 ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
210 addr2func(ret_addr);
211 }
212 else
213 {
214 printf("top stack frame\n");
215 }
216
217 printf("======\n");
218
219
220 void* frame_address = __builtin_frame_address(0);
221 if (frame_address)
222 printf("Frame 0 address of f3: %p\n", frame_address);
223
224 void* return_address = __builtin_return_address(0);
225 if (return_address)
226 printf("Return 0 address of f3: %p\n", return_address);
227 }
228 #endif
229 }
230
231 static void * sig_thread (void *arg)
232 {
233 sigset_t *set = arg;
234 int s, sig;
235
236 printf("sig thread pid: %d\n", getpid());
237
238 for (;;)
239 {
240 s = sigwait (set, &sig);
241 if (s != 0)
242 handle_error_en (s, "sigwait");
243 printf ("Signal handling thread got signal %d\n", sig);
244 }
245 return 0;
246 }
247
248 static void fun_123(void *arg)
249 {
250 sigset_t *set = arg;
251 int s, sig;
252 printf("123 thread pid: %d\n", getpid());
253 printf("123 thread tid: %d\n", gettid());
254
255 while(1)
256 {
257 //printf("123\n");
258
259
260 #if 0
261 for (;;)
262 {
263 s = sigwait (set, &sig);
264 if (s != 0)
265 handle_error_en (s, "sigwait");
266 printf ("Signal handling thread got signal %d\n", sig);
267 print_backtrace();
268 }
269 #endif
270 }
271 }
272
273 static void fun_12(void *arg)
274 {
275 fun_123(arg);
276 }
277
278 static void * fun_1(void *arg)
279 {
280 //for (;;)
281 {
282 fun_12(arg);
283 #if 0
284 printf("11 sig thread pid: %d\n", getpid());
285 printf("11 sig thread tid: %d\n", gettid());
286 #endif
287 }
288 return 0;
289 }
290
291 static void fun_223()
292 {
293 while(1)
294 {
295 //printf("223\n");
296 }
297 }
298
299 static void fun_22()
300 {
301 fun_223();
302 }
303
304 static void * fun_2(void *arg)
305 {
306 struct sched_param schedp;
307
308 memset(&schedp, 0, sizeof(schedp));
309 schedp.sched_priority = 39;
310 sched_setscheduler(0, SCHED_RR, &schedp);
311
312 for (;;)
313 {
314 fun_22();
315 #if 0
316 printf("22 sig thread pid: %d\n", getpid());
317 printf("22 sig thread tid: %d\n", gettid());
318 #endif
319 }
320 return 0;
321 }
322
323 int main(int argc, char *argv[])
324 {
325 //pthread_t thread;
326 pthread_t thread_1;
327 //pthread_t thread_2;
328
329 //printf ("Caller name: %p\n", __builtin_return_address(0));
330 printf("main: %p, fun_1: %p, fun_2: %p\n", main, fun_1, fun_2);
331
332
333 struct sigaction sa;
334 sa.sa_handler = sig_handler;
335 sigemptyset(&sa.sa_mask);
336 sa.sa_flags = 0;
337
338 //signal(SIGUSR1, sig_handler);
339
340
341 #if 1
342 //if (sigaction(SIGINT, &sa, NULL) == -1) {
343 if (sigaction(SIGUSR1, &sa, NULL) == -1) {
344 perror("sigaction");
345 exit(EXIT_FAILURE);
346 }
347 #endif
348 sigset_t set;
349 int s;
350
351 /* Block SIGQUIT and SIGUSR1; other threads created by main()
352 will inherit a copy of the signal mask. */
353
354 printf("pid: %d\n", getpid());
355
356 #if 0
357 sigemptyset (&set);
358 //sigaddset (&set, SIGQUIT);
359 sigaddset (&set, SIGUSR1);
360 s = pthread_sigmask (SIG_BLOCK, &set, NULL);
361 if (s != 0)
362 handle_error_en (s, "pthread_sigmask");
363 #endif
364
365 #if 0
366 s = pthread_create (&thread, NULL, &sig_thread, (void *) &set);
367 if (s != 0)
368 handle_error_en (s, "pthread_create");
369 #endif
370
371 s = pthread_create (&thread_1, NULL, &fun_1, (void *) &set);
372 if (s != 0)
373 handle_error_en (s, "pthread_create 1");
374
375 #if 0
376 s = pthread_create (&thread_2, NULL, &fun_2, (void *) &set);
377 if (s != 0)
378 handle_error_en (s, "pthread_create 2");
379
380 /* Main thread carries on to create other threads and/or do
381 other work */
382 #endif
383 //pause();
384 while(1)
385 {
386 printf("main thread pid: %d\n", getpid());
387 printf("main thread tid: %d\n", gettid());
388 fun_22();
389 #if 0
390 printf("main thread pid: %d\n", getpid());
391 printf("main thread tid: %d\n", gettid());
392 #endif
393 }
394 //pause (); /* Dummy pause so we can test program */
395 }
這個 __restore_rt function 的內容很簡單, 如 list 5, 一個 system call, 查了一下, 參考 list 6 - rt_sigreturn, man rt_sigreturn 可以知道其內容, 英文不好懂是吧, 讓 chatgpt 翻譯一下, 就容易理解了。
當 kernel 要執行 signal handle 要怎麼做呢? 現在的程式可是執行在 while 的地方, 有什麼辦法可以改到 t1.c L136 void sig_handler(int signo) 執行呢? 不好想吧, 當 t1 process 睡覺時要再次執行之前, kernel 做了手腳, 改動 rip, 讓 t1 process 從 sig_handler 執行, 當然也要在 stack frame 做一些操作, 否則 sig_handler 執行時, 會用到原本的 rsp, 也許會有問題, 一般會建立一個新的 stack frame 讓 sig_handler 在這邊操作 stack, 就不會影響到其他 function 的 stack frame。
當要從 signal handle 回到原本執行的地方 (此例子是回到 while) 要怎麼做呢? 有 2 種作法, signal handle return 時, stack return address 塞 while 的位址, 這樣就回去了。不過 linux kernel 不是這麼做, 用了一個奇特的作法。
一樣是安插了一個 return address, 在 signal handle return 之後會跑去執行 __restore_rt, 然後執行 rt_sigreturn system call 回到 kernel, 這時候再把原本 t1 要執行的 while 回復, 等到下次執行 t1 時, 就從 while 開始執行。
list 5 __restore_rt
1 0000000000405940 <__restore_rt>:
2 405940: 48 c7 c0 0f 00 00 00 mov $0xf,%rax
3 405947: 0f 05 syscall
4 405949: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
list 6
https://filippo.io/linux-syscall-table/
%rax Name Manual Entry point
15 rt_sigreturn rt_sigreturn(2) sys_rt_sigreturn
至於為什麼自己寫的 backtrace 查不到 fun_123, 簡單來說就是 __restore_rt 的 return address 不是 fun_123, 而是 fun_123 的上一層 fun_12, 自然從 stack frame 找不到 fun_123 這個資訊, 那為什麼 glibc backtrace 找得到呢? 因為它用了別的方法, 下一篇再來介紹這個魔法。
ref: