2024年4月12日 星期五

signal handle backtrace (0) - signal handle 如何 return

我不是無所不知, 只是剛好知道而已。
實做 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:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。