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:

2024年4月7日 星期日

ff7-reamek mod on steam deck

很久之前就一直很想用 mod, 但都找不到方法, 直到發現以下影片後終於知道要怎麼用 mod。



因為我玩 steam 遊戲幾乎都是用 steam deck, 怕這些 mod 在 steam deck/linux 上很難搞, 還好運氣不錯, 成功在 steam deck/linux 上執行這些 mod, 以下安裝路徑是以 steam deck 為主。影片介紹的是以 windows 路徑為主。

ff7-reamek mod 網址: https://www.nexusmods.com/finalfantasy7remake, 需要註冊才能下載 mod。

FFVIIHook 1.7-74-1-7-1708987498

2022/01/11  下午 06:13             1,480 Engine.ini
2024/02/27  上午 06:23            25,088 xinput1_3.dll
放到 /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Binaries/Win64
(deck@steamdeck Win64)$ ls "/home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Binaries/Win64"
Engine.ini  ff7remake_.exe  vkd3d-proton.cache  xinput1_3.dll


mod 檔案放到 /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/

我依照影片建議建立了 ~mods, /home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/~mods, 也和影片一樣先用潔西測試, 成功了。建議先照影片流程把
  1. 前置模組: FFVIIHook - INI and dev console unlocker
  2. 潔西模組: Hot Pink Jessie
安裝測試好, 再來玩其他模組。



/home/deck/.local/share/Steam/steamapps/common/FINAL FANTASY VII REMAKE/End/Content/Paks/~mods

我把 ~mods 改成 0-mods 有些 mod 會失效, 所以還是都用 ~mods, 看起來好像是個不成文規定的命名, 有些 mod 還會相依其他 mod, 比較麻煩。

另外也把存檔位置找出來, 30 個存檔不夠用, 只好自己備份。

save files: /home/deck/.local/share/Steam/steamapps/compatdata/1462040/pfx/drive_c/users/steamuser/Documents/My Games/FINAL FANTASY VII REMAKE/Steam/76561198098462161

設定成人 mod:
基本上從這邊進去 site preferences (右上角的按鈕)

Content blocking
Adult content
勾選 Show adult content

[模組MOD] 模組網站nexusmods,開啟成人內容,以及分享其他不重要的小事
[模組MOD] 艾莉絲裸裝 - FFVII-重製版
Tifa 4K Hi-Poly Nude Mod (tifa 裸體)

測試了好多 mod, 最後還是選國王的新衣。



綠意公園 tifa, aerith 那段, 短短2分鐘動畫效果超棒! 同時可以看到 tifa, aerith, 可惜不好放上來。

使用這些遊戲 mod 還蠻有門檻的, 不是無腦就可以搞定, 要花時間查資料。

在 steam deck 上操作不像 windows 方便, 除非你接鍵盤滑鼠外接螢幕又進入桌面模式, 我是用 ssh 登入, 直接打指令操作, 另外可以使用 symbolic link 就不用一直搬移檔案, 如 list 5。

list 5
(deck@steamdeck aerith)$ ls -l
lrwxrwxrwx 1 deck deck 82 Apr  7 09:56 zAerithBunny.pak -> '/home/deck/ff7-org-mods/mods/Aerith Bunny Girl-868-1-0-1649964294/zAerithBunny.pak'

另外由於路徑很長, 用了 symbolic link 縮短路徑, 如 list 6。

list 6
(deck@steamdeck ~)$ ls -l ff7-remake
lrwxrwxrwx 1 deck deck 55 Apr  5 11:17 ff7-remake -> '.steam/steam/steamapps/common/FINAL FANTASY VII REMAKE/'
(deck@steamdeck ~)$ ls -l ff7-paks
lrwxrwxrwx 1 deck deck 28 Apr  5 12:40 ff7-paks -> ff7-remake/End/Content/Paks/
fig 8. ff7-remake mod on steam deck

以下的表情很有趣, cloud 看到 aerith 打扮的很漂亮的表情 (正常男人看到女人裸體應該都是這表情); aerith 看到 cloud 打扮的很漂亮的表情。

fig 5 aerith 在按摩館的打扮 fig 6 看到aerith 在按摩館的打扮後的神情

fig 7 aerith 看到 cloud 女裝的神情 fig 8 cloud 女裝




Bee costume mod 蜜蜂裝也不錯, 玩起來不尷尬。

ref:
【情報】FF7R PC版MOD始動 (2022/06/18 更新STEAM版設定與控制台支援 )

2024年4月3日 星期三

Sweet Baby Inc detected, 遊戲政確推手 Sweet Baby





Sweet Baby 是令遊戲玩家噁心的組織, 簡稱她們 sb 好了, 政治政確這種事情想不到也傳到遊戲產業。還好有《劍星》出來拯救玩家, 希望《劍星》能靠銷售量讓那些搞不清楚的遊戲公司清醒, 不要亂加入政治政確的東西。本來對政治政確的元素沒那麼反感, 現在只要看到遊戲有這些元素, 就不想買了, 不管你的原本設計是這樣還是政治政確的關係 (因為我分不出來是哪種), 玩個遊戲也要這麼累, 真煩。

打著理想的口號做噁心的事情, 還偷偷摸摸隱藏在台面下。

另外也會參考 ref 2 的不推薦遊戲 (有被 SB 染指的遊戲), 列在上面的都不會買, 管你是幾A大作。至少有神鬼寓言4, 最后的生还者2 , 漫威蜘蛛侠2 , 战神5, 消逝的光芒2。

另外懷疑 ff7-remake 以下的部份可能是政治政確。





fig 5. 應該是比較正常的女生角色, 另外2個有點醜化和黑色膚種, 另外是 cloud 女裝, 希望是我自己想太多。不過 cloud 女裝這段其實蠻有趣。

fig 5. 比較正常的女生角色





ref:
  1. 遊戲政確推手Sweet Baby組織不滿被列Steam黑名單,員工出征玩家反引起多方關注
  2. Sweet Baby Inc detected
  3. [新聞] 馬斯克批Sweet Baby是電玩產業的禍害

2024年3月31日 星期日

tpass 購買與設定

「TPASS 行政院通勤月票一卡通」為「行政院促進公共運輸使用方案」, 購買和設定不是很容易, 紀錄一下。

購買方式不是很方便的從那個地點直接購買, 需要預購, 用 ibon 或是上網購買, 也就是要等卡片寄來, 很麻煩, 所以我直接去捷運站購買, 在捷運站服務處可以直接購得, 所以不在捷運站區域的人, 就只能線上購買。

登入會員設定, 需要使用 app 設定這張卡, 還要實名認證, 需要卡片的照片, 身份證的照片, 我之前因為實名問題不太想用, 最後還是因為價錢屈服了。

有各種方案的儲值, 我是用 399 臺鐵方案, 一樣在捷運站服務處處理, 還需要指定一個月的區間, 例如 20240301 ~ 20240331。

為了怕那個步驟搞不定, 我在捷運站完成這3個步驟, 才離開, 然後到臺鐵測試, tpass 可以正常使用。

若是用 app 儲值, 還需要到 ibon 登記卡片的一個月使用區間。目前似乎也可以用手機 NFC 過卡了。

這個補助對固定搭乘大眾交通工具的人幫助很大, 省下不少錢, 以我自己一個月來說大概省 600-399 = 201, 但我算是短程, 長程會省更多。應該也會多鼓勵人們搭乘大眾運輸工具。沒用到的人可能會不太爽, 畢竟是用納稅人的錢補助。

2024年3月21日 星期四

實做 c backtrace

c 的 backtrace 是在 call function 時, 如何得知誰 call 了這個 function。

c 的 backtrace 如何做到, 問 chatgpt 馬上就給出 list 1. 的範例程式, 真的好用, 以 list 2 來說:
_start -> __libc_start_main -> main -> f1 -> f2 -> f3 -> print_backtrace
會得到
xx [0x400b7b]
xx [0x400d60]
xx [0x401087]
xx [0x4010a4]
xx [0x4010c0]
xx [0x401689]
xx [0x400a5a]
使用 addr2line 可以查到對應的 function
addr2line -f -e t2 0x400b7b
print_backtrace
/home/descent/git/progs/backtrace/t2.c:15

addr2line -f -e t2 0x400a5a
_start
??:?
會得到: print_backtrace -> f3 -> f2 -> f1 -> main -> __libc_start_main -> _start

但如果再問 chatgpt 要怎麼實做 backtrace(), backtrace_symbols(), 它就鬼打牆了。

所以如果不借助 backtrace(), backtrace_symbols() 要怎麼辦到呢?

這和平台有關, 本篇是在 x86_64 環境的實做。

list 1. print_backtrace()
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <stdint.h>
  4 
  5 #include <execinfo.h>
  6 
  7 #define BT_BUF_SIZE 100
  8 
  9 void print_backtrace() 
 10 {
 11     void *buffer[BT_BUF_SIZE];
 12     char **strings;
 13     int nptrs;
 14 
 15     nptrs = backtrace(buffer, BT_BUF_SIZE);
 16     printf("backtrace() returned %d addresses\n", nptrs);
 17 
 18     strings = backtrace_symbols(buffer, nptrs);
 19     if (strings == NULL) {
 20         perror("backtrace_symbols");
 21         exit(EXIT_FAILURE);
 22     }
 23 
 24     for (int i = 0; i < nptrs; i++) {
 25         printf("xx %s\n", strings[i]);
 26     }
 27 
 28     free(strings);
 29 }
list 2. t2.c
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <stdint.h>
  4 
  5 #include <execinfo.h>
  6 
  7 #define BT_BUF_SIZE 100
  8 
  9 void print_backtrace() 
 10 {
 11     void *buffer[BT_BUF_SIZE];
 12     char **strings;
 13     int nptrs;
 14 
 15     nptrs = backtrace(buffer, BT_BUF_SIZE);
 16     printf("backtrace() returned %d addresses\n", nptrs);
 17 
 18     strings = backtrace_symbols(buffer, nptrs);
 19     if (strings == NULL) {
 20         perror("backtrace_symbols");
 21         exit(EXIT_FAILURE);
 22     }
 23 
 24     for (int i = 0; i < nptrs; i++) {
 25         printf("xx %s\n", strings[i]);
 26     }
 27 
 28     free(strings);
 29 }
 30 
 31 void f5()
 32 {
 33   printf("in f5\n");
 34   print_backtrace();
 35 }
 36 
 37 
 38 #include <stdio.h>
 39 
 40 uintptr_t get_rip_value() 
 41 {
 42   //uintptr_t rip_value;
 43   //asm volatile("movq $0, %%rax; movq (%%rsp), %%rax" : "=a" (rip_value));
 44   //asm("mov %%rip, %0" : "=r" (rip_value));
 45   uintptr_t current_address;
 46   asm("lea (%%rip), %0" : "=r" (current_address));
 47   return current_address;
 48 }
 49 
 50 unsigned long get_rbp()
 51 {
 52   unsigned long rbp_value;
 53   asm("movq %%rbp, %0" : "=r" (rbp_value));
 54   printf("The value of RBP register is: %#lx\n", rbp_value);
 55   return rbp_value;
 56 }
 57 
 58 int addr2func(uintptr_t addr)
 59 {
 60   char cmd[128] = {0};
 61   sprintf(cmd, "addr2line -f -e t2 %#lx\n", addr);
 62   printf("cmd: %s\n", cmd);
 63   system(cmd);
 64   return 0;
 65 }
 66 
 67 void f3()
 68 {
 69   print_backtrace();
 70   #if 1
 71   int level = 0;
 72   printf("in f3\n");
 73   //while(1)
 74   {
 75     {
 76       uintptr_t current_address;
 77       asm("lea (%%rip), %0" : "=r" (current_address));
 78       printf("current_address : %#lx\n", current_address);
 79       addr2func(current_address);
 80 
 81       printf("======\n");
 82 
 83       unsigned long rbp_value, previous_rbp;
 84       asm("movq %%rbp, %0" : "=r" (rbp_value));
 85       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
 86       ++level;
 87 
 88       uintptr_t ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
 89       printf("ret_addr : %#lx\n", ret_addr);
 90 
 91       addr2func(ret_addr);
 92 
 93       printf("======\n");
 94 
 95       rbp_value = *(uintptr_t*)(rbp_value);
 96       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
 97       ++level;
 98 
 99       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
100       addr2func(ret_addr);
101 
102 
103       printf("======\n");
104 
105       rbp_value = *(uintptr_t*)(rbp_value);
106       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
107       ++level;
108 
109       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
110       addr2func(ret_addr);
111 
112       printf("======\n");
113 
114       rbp_value = *(uintptr_t*)(rbp_value);
115       printf("%d ## The value of RBP register is: %#lx\n", level, rbp_value);
116       ++level;
117 
118       ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
119       addr2func(ret_addr);
120 
121       printf("======\n");
122 
123       previous_rbp = rbp_value;
124       
125       rbp_value = *(uintptr_t*)(rbp_value);
126       printf("%d ## The value of RBP register is: %#lx, previous_rbp: %#lx\n", level, rbp_value, previous_rbp);
127       ++level;
128 
129       if (rbp_value > previous_rbp)
130       {
131         ret_addr = *(uintptr_t*)(rbp_value + sizeof(uintptr_t));
132         addr2func(ret_addr);
133       }
134       else
135       {
136         printf("top stack frame\n");
137       }
138 
139       printf("======\n");
140 
141 
142     void* frame_address = __builtin_frame_address(0);
143     if (frame_address)
144       printf("Frame 0 address of f3: %p\n", frame_address);
145 
146     void* return_address = __builtin_return_address(0);
147     if (return_address)
148       printf("Return 0 address of f3: %p\n", return_address);
149     }
150 
151 #if 1
152     {
153     void* frame_address = __builtin_frame_address(1);
154     if (frame_address)
155       printf("Frame 1 address of f3: %p\n", frame_address);
156 
157     void* return_address = __builtin_return_address(1);
158     if (return_address)
159       printf("Return 1 address of f3: %p\n", return_address);
160     }
161 
162     {
163     void* frame_address = __builtin_frame_address(2);
164     if (frame_address)
165       printf("Frame 2 address of f3: %p\n", frame_address);
166 
167     void* return_address = __builtin_return_address(2);
168     if (return_address)
169       printf("Return 2 address of f3: %p\n", return_address);
170     }
171 #endif
172 #if 0
173     {
174     void* frame_address = __builtin_frame_address(3);
175     if (frame_address)
176       printf("Frame 3 address of f2: %p\n", frame_address);
177 
178     void* return_address = __builtin_return_address(3);
179     if (return_address)
180       printf("Return 3 address of f2: %p\n", return_address);
181     }
182 #endif
183 
184   }
185 #endif
186 
187 #if 0
188   printf("in f2\n");
189   f3();
190 #endif
191 }
192 
193 void f2()
194 {
195   printf("in f2\n");
196   f3();
197 }
198 
199 void f1()
200 {
201   printf("in f1\n");
202   f2();
203 }
204 
205 int main(int argc, char *argv[])
206 {
207   f1(); 
208   printf("main: %p\n", main);
209   printf("f1: %p\n", f1);
210   printf("f2: %p\n", f2);
211   #if 0
212   printf("f3: %p\n", f3);
213   printf("f5: %p\n", f5);
214   #endif
215   return 0;
216 }


先來理解 c 語言呼叫 function 時做的動作, 參考 list 3 的反組譯。

list 3. objdump -d t2
     1 
     2 t2:     file format elf64-x86-64
     3 

   611 0000000000400c50 <f5>:
   612   400c50:	55                   	push   %rbp
   613   400c51:	48 89 e5             	mov    %rsp,%rbp
   614   400c54:	48 8d 3d 49 2c 09 00 	lea    0x92c49(%rip),%rdi        # 4938a4 <_IO_stdin_used+0x44>
   615   400c5b:	e8 f0 06 01 00       	callq  411350 <_IO_puts>
   616   400c60:	b8 00 00 00 00       	mov    $0x0,%eax
   617   400c65:	e8 e3 fe ff ff       	callq  400b4d <print_backtrace>
   618   400c6a:	90                   	nop
   619   400c6b:	5d                   	pop    %rbp
   620   400c6c:	c3                   	retq   
   621 
   682 
   683 0000000000400d4e <f3>:
   684   400d4e:	55                   	push   %rbp
   685   400d4f:	48 89 e5             	mov    %rsp,%rbp
   686   400d52:	48 83 ec 60          	sub    $0x60,%rsp
   687   400d56:	b8 00 00 00 00       	mov    $0x0,%eax
   688   400d5b:	e8 ed fd ff ff       	callq  400b4d <print_backtrace>
   689   400d60:	c7 45 ac 00 00 00 00 	movl   $0x0,-0x54(%rbp)
   690   400d67:	48 8d 3d 88 2b 09 00 	lea    0x92b88(%rip),%rdi        # 4938f6 <_IO_stdin_used+0x96>
   691   400d6e:	e8 dd 05 01 00       	callq  411350 <_IO_puts>
   692   400d73:	48 8d 05 00 00 00 00 	lea    0x0(%rip),%rax        # 400d7a <f3+0x2c>
   693   400d7a:	48 89 45 b0          	mov    %rax,-0x50(%rbp)
   694   400d7e:	48 8b 45 b0          	mov    -0x50(%rbp),%rax
   695   400d82:	48 89 c6             	mov    %rax,%rsi
   696   400d85:	48 8d 3d 70 2b 09 00 	lea    0x92b70(%rip),%rdi        # 4938fc <_IO_stdin_used+0x9c>
   697   400d8c:	b8 00 00 00 00       	mov    $0x0,%eax
   698   400d91:	e8 fa f3 00 00       	callq  410190 <_IO_printf>
   699   400d96:	48 8b 45 b0          	mov    -0x50(%rbp),%rax
   700   400d9a:	48 89 c7             	mov    %rax,%rdi
   701   400d9d:	e8 0d ff ff ff       	callq  400caf <addr2func>
   702   400da2:	48 8d 3d 6b 2b 09 00 	lea    0x92b6b(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   703   400da9:	e8 a2 05 01 00       	callq  411350 <_IO_puts>
   704   400dae:	48 89 e8             	mov    %rbp,%rax
   705   400db1:	48 89 45 b8          	mov    %rax,-0x48(%rbp)
   706   400db5:	48 8b 55 b8          	mov    -0x48(%rbp),%rdx
   707   400db9:	8b 45 ac             	mov    -0x54(%rbp),%eax
   708   400dbc:	89 c6                	mov    %eax,%esi
   709   400dbe:	48 8d 3d 5b 2b 09 00 	lea    0x92b5b(%rip),%rdi        # 493920 <_IO_stdin_used+0xc0>
   710   400dc5:	b8 00 00 00 00       	mov    $0x0,%eax
   711   400dca:	e8 c1 f3 00 00       	callq  410190 <_IO_printf>
   712   400dcf:	83 45 ac 01          	addl   $0x1,-0x54(%rbp)
   713   400dd3:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   714   400dd7:	48 83 c0 08          	add    $0x8,%rax
   715   400ddb:	48 8b 00             	mov    (%rax),%rax
   716   400dde:	48 89 45 c0          	mov    %rax,-0x40(%rbp)
   717   400de2:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   718   400de6:	48 89 c6             	mov    %rax,%rsi
   719   400de9:	48 8d 3d 5a 2b 09 00 	lea    0x92b5a(%rip),%rdi        # 49394a <_IO_stdin_used+0xea>
   720   400df0:	b8 00 00 00 00       	mov    $0x0,%eax
   721   400df5:	e8 96 f3 00 00       	callq  410190 <_IO_printf>
   722   400dfa:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   723   400dfe:	48 89 c7             	mov    %rax,%rdi
   724   400e01:	e8 a9 fe ff ff       	callq  400caf <addr2func>
   725   400e06:	48 8d 3d 07 2b 09 00 	lea    0x92b07(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   726   400e0d:	e8 3e 05 01 00       	callq  411350 <_IO_puts>
   727   400e12:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   728   400e16:	48 8b 00             	mov    (%rax),%rax
   729   400e19:	48 89 45 b8          	mov    %rax,-0x48(%rbp)
   730   400e1d:	48 8b 55 b8          	mov    -0x48(%rbp),%rdx
   731   400e21:	8b 45 ac             	mov    -0x54(%rbp),%eax
   732   400e24:	89 c6                	mov    %eax,%esi
   733   400e26:	48 8d 3d f3 2a 09 00 	lea    0x92af3(%rip),%rdi        # 493920 <_IO_stdin_used+0xc0>
   734   400e2d:	b8 00 00 00 00       	mov    $0x0,%eax
   735   400e32:	e8 59 f3 00 00       	callq  410190 <_IO_printf>
   736   400e37:	83 45 ac 01          	addl   $0x1,-0x54(%rbp)
   737   400e3b:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   738   400e3f:	48 83 c0 08          	add    $0x8,%rax
   739   400e43:	48 8b 00             	mov    (%rax),%rax
   740   400e46:	48 89 45 c0          	mov    %rax,-0x40(%rbp)
   741   400e4a:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   742   400e4e:	48 89 c7             	mov    %rax,%rdi
   743   400e51:	e8 59 fe ff ff       	callq  400caf <addr2func>
   744   400e56:	48 8d 3d b7 2a 09 00 	lea    0x92ab7(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   745   400e5d:	e8 ee 04 01 00       	callq  411350 <_IO_puts>
   746   400e62:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   747   400e66:	48 8b 00             	mov    (%rax),%rax
   748   400e69:	48 89 45 b8          	mov    %rax,-0x48(%rbp)
   749   400e6d:	48 8b 55 b8          	mov    -0x48(%rbp),%rdx
   750   400e71:	8b 45 ac             	mov    -0x54(%rbp),%eax
   751   400e74:	89 c6                	mov    %eax,%esi
   752   400e76:	48 8d 3d a3 2a 09 00 	lea    0x92aa3(%rip),%rdi        # 493920 <_IO_stdin_used+0xc0>
   753   400e7d:	b8 00 00 00 00       	mov    $0x0,%eax
   754   400e82:	e8 09 f3 00 00       	callq  410190 <_IO_printf>
   755   400e87:	83 45 ac 01          	addl   $0x1,-0x54(%rbp)
   756   400e8b:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   757   400e8f:	48 83 c0 08          	add    $0x8,%rax
   758   400e93:	48 8b 00             	mov    (%rax),%rax
   759   400e96:	48 89 45 c0          	mov    %rax,-0x40(%rbp)
   760   400e9a:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   761   400e9e:	48 89 c7             	mov    %rax,%rdi
   762   400ea1:	e8 09 fe ff ff       	callq  400caf <addr2func>
   763   400ea6:	48 8d 3d 67 2a 09 00 	lea    0x92a67(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   764   400ead:	e8 9e 04 01 00       	callq  411350 <_IO_puts>
   765   400eb2:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   766   400eb6:	48 8b 00             	mov    (%rax),%rax
   767   400eb9:	48 89 45 b8          	mov    %rax,-0x48(%rbp)
   768   400ebd:	48 8b 55 b8          	mov    -0x48(%rbp),%rdx
   769   400ec1:	8b 45 ac             	mov    -0x54(%rbp),%eax
   770   400ec4:	89 c6                	mov    %eax,%esi
   771   400ec6:	48 8d 3d 53 2a 09 00 	lea    0x92a53(%rip),%rdi        # 493920 <_IO_stdin_used+0xc0>
   772   400ecd:	b8 00 00 00 00       	mov    $0x0,%eax
   773   400ed2:	e8 b9 f2 00 00       	callq  410190 <_IO_printf>
   774   400ed7:	83 45 ac 01          	addl   $0x1,-0x54(%rbp)
   775   400edb:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   776   400edf:	48 83 c0 08          	add    $0x8,%rax
   777   400ee3:	48 8b 00             	mov    (%rax),%rax
   778   400ee6:	48 89 45 c0          	mov    %rax,-0x40(%rbp)
   779   400eea:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   780   400eee:	48 89 c7             	mov    %rax,%rdi
   781   400ef1:	e8 b9 fd ff ff       	callq  400caf <addr2func>
   782   400ef6:	48 8d 3d 17 2a 09 00 	lea    0x92a17(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   783   400efd:	e8 4e 04 01 00       	callq  411350 <_IO_puts>
   784   400f02:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   785   400f06:	48 89 45 c8          	mov    %rax,-0x38(%rbp)
   786   400f0a:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   787   400f0e:	48 8b 00             	mov    (%rax),%rax
   788   400f11:	48 89 45 b8          	mov    %rax,-0x48(%rbp)
   789   400f15:	48 8b 4d c8          	mov    -0x38(%rbp),%rcx
   790   400f19:	48 8b 55 b8          	mov    -0x48(%rbp),%rdx
   791   400f1d:	8b 45 ac             	mov    -0x54(%rbp),%eax
   792   400f20:	89 c6                	mov    %eax,%esi
   793   400f22:	48 8d 3d 37 2a 09 00 	lea    0x92a37(%rip),%rdi        # 493960 <_IO_stdin_used+0x100>
   794   400f29:	b8 00 00 00 00       	mov    $0x0,%eax
   795   400f2e:	e8 5d f2 00 00       	callq  410190 <_IO_printf>
   796   400f33:	83 45 ac 01          	addl   $0x1,-0x54(%rbp)
   797   400f37:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   798   400f3b:	48 3b 45 c8          	cmp    -0x38(%rbp),%rax
   799   400f3f:	76 1d                	jbe    400f5e <f3+0x210>
   800   400f41:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
   801   400f45:	48 83 c0 08          	add    $0x8,%rax
   802   400f49:	48 8b 00             	mov    (%rax),%rax
   803   400f4c:	48 89 45 c0          	mov    %rax,-0x40(%rbp)
   804   400f50:	48 8b 45 c0          	mov    -0x40(%rbp),%rax
   805   400f54:	48 89 c7             	mov    %rax,%rdi
   806   400f57:	e8 53 fd ff ff       	callq  400caf <addr2func>
   807   400f5c:	eb 0c                	jmp    400f6a <f3+0x21c>
   808   400f5e:	48 8d 3d 39 2a 09 00 	lea    0x92a39(%rip),%rdi        # 49399e <_IO_stdin_used+0x13e>
   809   400f65:	e8 e6 03 01 00       	callq  411350 <_IO_puts>
   810   400f6a:	48 8d 3d a3 29 09 00 	lea    0x929a3(%rip),%rdi        # 493914 <_IO_stdin_used+0xb4>
   811   400f71:	e8 da 03 01 00       	callq  411350 <_IO_puts>
   812   400f76:	48 89 6d d0          	mov    %rbp,-0x30(%rbp)
   813   400f7a:	48 83 7d d0 00       	cmpq   $0x0,-0x30(%rbp)
   814   400f7f:	74 18                	je     400f99 <f3+0x24b>
   815   400f81:	48 8b 45 d0          	mov    -0x30(%rbp),%rax
   816   400f85:	48 89 c6             	mov    %rax,%rsi
   817   400f88:	48 8d 3d 1f 2a 09 00 	lea    0x92a1f(%rip),%rdi        # 4939ae <_IO_stdin_used+0x14e>
   818   400f8f:	b8 00 00 00 00       	mov    $0x0,%eax
   819   400f94:	e8 f7 f1 00 00       	callq  410190 <_IO_printf>
   820   400f99:	48 8b 45 08          	mov    0x8(%rbp),%rax
   821   400f9d:	48 89 45 d8          	mov    %rax,-0x28(%rbp)
   822   400fa1:	48 83 7d d8 00       	cmpq   $0x0,-0x28(%rbp)
   823   400fa6:	74 18                	je     400fc0 <f3+0x272>
   824   400fa8:	48 8b 45 d8          	mov    -0x28(%rbp),%rax
   825   400fac:	48 89 c6             	mov    %rax,%rsi
   826   400faf:	48 8d 3d 13 2a 09 00 	lea    0x92a13(%rip),%rdi        # 4939c9 <_IO_stdin_used+0x169>
   827   400fb6:	b8 00 00 00 00       	mov    $0x0,%eax
   828   400fbb:	e8 d0 f1 00 00       	callq  410190 <_IO_printf>
   829   400fc0:	48 8b 45 00          	mov    0x0(%rbp),%rax
   830   400fc4:	48 89 45 e0          	mov    %rax,-0x20(%rbp)
   831   400fc8:	48 83 7d e0 00       	cmpq   $0x0,-0x20(%rbp)
   832   400fcd:	74 18                	je     400fe7 <f3+0x299>
   833   400fcf:	48 8b 45 e0          	mov    -0x20(%rbp),%rax
   834   400fd3:	48 89 c6             	mov    %rax,%rsi
   835   400fd6:	48 8d 3d 08 2a 09 00 	lea    0x92a08(%rip),%rdi        # 4939e5 <_IO_stdin_used+0x185>
   836   400fdd:	b8 00 00 00 00       	mov    $0x0,%eax
   837   400fe2:	e8 a9 f1 00 00       	callq  410190 <_IO_printf>
   838   400fe7:	48 8b 45 00          	mov    0x0(%rbp),%rax
   839   400feb:	48 8b 40 08          	mov    0x8(%rax),%rax
   840   400fef:	48 89 45 e8          	mov    %rax,-0x18(%rbp)
   841   400ff3:	48 83 7d e8 00       	cmpq   $0x0,-0x18(%rbp)
   842   400ff8:	74 18                	je     401012 <f3+0x2c4>
   843   400ffa:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
   844   400ffe:	48 89 c6             	mov    %rax,%rsi
   845   401001:	48 8d 3d f8 29 09 00 	lea    0x929f8(%rip),%rdi        # 493a00 <_IO_stdin_used+0x1a0>
   846   401008:	b8 00 00 00 00       	mov    $0x0,%eax
   847   40100d:	e8 7e f1 00 00       	callq  410190 <_IO_printf>
   848   401012:	48 8b 45 00          	mov    0x0(%rbp),%rax
   849   401016:	48 8b 00             	mov    (%rax),%rax
   850   401019:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
   851   40101d:	48 83 7d f0 00       	cmpq   $0x0,-0x10(%rbp)
   852   401022:	74 18                	je     40103c <f3+0x2ee>
   853   401024:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
   854   401028:	48 89 c6             	mov    %rax,%rsi
   855   40102b:	48 8d 3d ea 29 09 00 	lea    0x929ea(%rip),%rdi        # 493a1c <_IO_stdin_used+0x1bc>
   856   401032:	b8 00 00 00 00       	mov    $0x0,%eax
   857   401037:	e8 54 f1 00 00       	callq  410190 <_IO_printf>
   858   40103c:	48 8b 45 00          	mov    0x0(%rbp),%rax
   859   401040:	48 8b 00             	mov    (%rax),%rax
   860   401043:	48 8b 40 08          	mov    0x8(%rax),%rax
   861   401047:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
   862   40104b:	48 83 7d f8 00       	cmpq   $0x0,-0x8(%rbp)
   863   401050:	74 18                	je     40106a <f3+0x31c>
   864   401052:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
   865   401056:	48 89 c6             	mov    %rax,%rsi
   866   401059:	48 8d 3d d7 29 09 00 	lea    0x929d7(%rip),%rdi        # 493a37 <_IO_stdin_used+0x1d7>
   867   401060:	b8 00 00 00 00       	mov    $0x0,%eax
   868   401065:	e8 26 f1 00 00       	callq  410190 <_IO_printf>
   869   40106a:	90                   	nop
   870   40106b:	c9                   	leaveq 
   871   40106c:	c3                   	retq   
   872 
   873 000000000040106d <f2>:
   874   40106d:	55                   	push   %rbp
   875   40106e:	48 89 e5             	mov    %rsp,%rbp
   876   401071:	48 8d 3d db 29 09 00 	lea    0x929db(%rip),%rdi        # 493a53 <_IO_stdin_used+0x1f3>
   877   401078:	e8 d3 02 01 00       	callq  411350 <_IO_puts>
   878   40107d:	b8 00 00 00 00       	mov    $0x0,%eax
   879   401082:	e8 c7 fc ff ff       	callq  400d4e <f3>
   880   401087:	90                   	nop
   881   401088:	5d                   	pop    %rbp
   882   401089:	c3                   	retq   
   883 
   884 000000000040108a <f1>:
   885   40108a:	55                   	push   %rbp
   886   40108b:	48 89 e5             	mov    %rsp,%rbp
   887   40108e:	48 8d 3d c4 29 09 00 	lea    0x929c4(%rip),%rdi        # 493a59 <_IO_stdin_used+0x1f9>
   888   401095:	e8 b6 02 01 00       	callq  411350 <_IO_puts>
   889   40109a:	b8 00 00 00 00       	mov    $0x0,%eax
   890   40109f:	e8 c9 ff ff ff       	callq  40106d <f2>
   891   4010a4:	90                   	nop
   892   4010a5:	5d                   	pop    %rbp
   893   4010a6:	c3                   	retq   
   894 
   895 00000000004010a7 <main>:
   896   4010a7:	55                   	push   %rbp
   897   4010a8:	48 89 e5             	mov    %rsp,%rbp
   898   4010ab:	48 83 ec 10          	sub    $0x10,%rsp
   899   4010af:	89 7d fc             	mov    %edi,-0x4(%rbp)
   900   4010b2:	48 89 75 f0          	mov    %rsi,-0x10(%rbp)
   901   4010b6:	b8 00 00 00 00       	mov    $0x0,%eax
   902   4010bb:	e8 ca ff ff ff       	callq  40108a <f1>
   903   4010c0:	48 8d 35 e0 ff ff ff 	lea    -0x20(%rip),%rsi        # 4010a7 <main>
   904   4010c7:	48 8d 3d 91 29 09 00 	lea    0x92991(%rip),%rdi        # 493a5f <_IO_stdin_used+0x1ff>
   905   4010ce:	b8 00 00 00 00       	mov    $0x0,%eax
   906   4010d3:	e8 b8 f0 00 00       	callq  410190 <_IO_printf>
   907   4010d8:	48 8d 35 ab ff ff ff 	lea    -0x55(%rip),%rsi        # 40108a <f1>
   908   4010df:	48 8d 3d 83 29 09 00 	lea    0x92983(%rip),%rdi        # 493a69 <_IO_stdin_used+0x209>
   909   4010e6:	b8 00 00 00 00       	mov    $0x0,%eax
   910   4010eb:	e8 a0 f0 00 00       	callq  410190 <_IO_printf>
   911   4010f0:	48 8d 35 76 ff ff ff 	lea    -0x8a(%rip),%rsi        # 40106d <f2>
   912   4010f7:	48 8d 3d 73 29 09 00 	lea    0x92973(%rip),%rdi        # 493a71 <_IO_stdin_used+0x211>
   913   4010fe:	b8 00 00 00 00       	mov    $0x0,%eax
   914   401103:	e8 88 f0 00 00       	callq  410190 <_IO_printf>
   915   401108:	b8 00 00 00 00       	mov    $0x0,%eax
   916   40110d:	c9                   	leaveq 
   917   40110e:	c3                   	retq   

L902 main call f1, L884 ~ L886 是進入 f1 時做的事情。
   902   4010bb:	e8 ca ff ff ff       	callq  40108a <f1>

main call f1 時, f1 會做
   884 000000000040108a <f1>:
   885   40108a:	55                   	push   %rbp
   886   40108b:	48 89 e5             	mov    %rsp,%rbp


   890   40109f:	e8 c9 ff ff ff       	callq  40106d <f2>

f1 call f2 時, f2 會做
   873 000000000040106d <f2>:
   874   40106d:	55                   	push   %rbp
   875   40106e:	48 89 e5             	mov    %rsp,%rbp
push   %rbp
mov    %rsp,%rbp
都是在 function 的最開始時會做的事情 (table 1. L2, L4)。table 1. main call f1, f1 call f2 把 rsp 的內容整理起來。

table 1. main call f1; f1 call f2 stack 內容
0 sp
1call f1 0x7fffffffe058 ret addr 0x4010c0
2 push %rbp (main_rbp) ; f1_rbp=rsp = 0x7fffffffe050 0x7fffffffe050 main_rbp (0x7fffffffe070)
3call f2 0x7fffffffe048 ret addr 0x4010a4
4 push %rbp (f1_rbp) ; f2_rbp=rsp = 0x7fffffffe040 0x7fffffffe040 f1_rbp (0x7fffffffe050)

指令 call 會發生的 sp 操作: rsp - 8, 再把 ret address 放入 rsp, 我用 gdb 把從 main 到 f2 時的 stack 內容記錄在 table 1。

list 5. t2 的編譯指令
gcc -static -Wall -save-temps -g -no-pie t2.c -o t2


用了 -no-pie 是希望不要編譯成 relocation 的執行檔, 用 objdump 在對照位址時比較方便。其他編譯選項沒太大影響。

原理是這樣, 先抓到目前 rbp 的值, 假如目前在 f2, 抓到 f2_rpb 就可以知道 f1 function 的 rpb f1_rpb, 知道了 f1_rpb 就可以知道 main function 的 rpb, 那麼知道每個 function 的 rbp 要幹麻呢? 為了取得 return address, 怎麼取得, 每層 function 的 rbp + 8 的位址就可以得到 (對照 table 1. L1, L3)。

寫成 c code 就是: 上一層 function 的 return address = *(uintptr_t*)(rpb + 8)

那麼又怎麼從目前 function 的 rpb 值得到上一層的 rpb 值呢? 從 rpb 值的位址取得 (參考 table 1. L2, L4), 寫成 c code 就是: 上一層 function 的 rpb = *(uintptr_t*)(rpb)

這樣一層一層追, 就可以追到 __libc_start_main, 那 _start 追得到嗎? 抱歉, 目前我還不知道怎麼從 __libc_start_main 追到 _start (backtrace(), backtrace_symbols() 可以追到 _start)。另外還有一個問題, 從這層的 rpb 一直追到上一層的 rpb, 要怎麼判定追到 __libc_start_main 這層了, 這邊是觀察出來的, 上一層的 rpb 應該會比目前這層的 rpb 大, 我就這樣判定, 有可能會出錯嗎? 當然有可能, 但我想不到別的辦法了。list 6 L105, L106 有類似的檢查條件。

list 2. L81 ~ L139 就是在做這樣的事情。另外還需要知道目前在那個 function, 所以用了 L77 得到目前的位址。這些抓暫存器、抓目前位址都是透過 chatgpt 問到, 相當方便。

再來有了位址要怎麼找出對應的 function, 這邊我偷懶了, 直接使用 addr2line 這個指令幫忙。光是 addr2line 怎麼辦到的, 可能又是一個主題了。

list 2 L142, L146 就是在取得 rbp 和 return address, __builtin_frame_address(), __builtin_return_address() 是 gcc 內建 function, 比較有可攜性。我不滿足這樣的作法, 想「知道」怎麼辦到的, 才有了本篇文章。

在 glibc 2.39 sysdeps/i386/backtrace.c __backtrace (void **array, int size) 可以看到類似的作法。

list 6. glibc-2.39/sysdeps/i386/backtrace.c
  1 /* Return backtrace of current program state.
  2    Copyright (C) 1998-2024 Free Software Foundation, Inc.
  3    This file is part of the GNU C Library.
  4
  5    The GNU C Library is free software; you can redistribute it and/or
  6    modify it under the terms of the GNU Lesser General Public
  7    License as published by the Free Software Foundation; either
  8    version 2.1 of the License, or (at your option) any later version.
  9
 10    The GNU C Library is distributed in the hope that it will be useful,
 11    but WITHOUT ANY WARRANTY; without even the implied warranty of
 12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13    Lesser General Public License for more details.
 14
 15    You should have received a copy of the GNU Lesser General Public
 16    License along with the GNU C Library; if not, see
 17    <https://www.gnu.org/licenses/>.  */
 18
 19 #include <execinfo.h>
 20 #include <stdlib.h>
 21 #include <unwind-link.h>
 22
 23 struct trace_arg
 24 {
 25   void **array;
 26   struct unwind_link *unwind_link;
 27   int cnt, size;
 28   void *lastebp, *lastesp;
 29 };
 30
 31 static _Unwind_Reason_Code
 32 backtrace_helper (struct _Unwind_Context *ctx, void *a)
 33 {
 34   struct trace_arg *arg = a;
 35
 36   /* We are first called with address in the __backtrace function.
 37      Skip it.  */
 38   if (arg->cnt != -1)
 39     arg->array[arg->cnt]
 40       = (void *) UNWIND_LINK_PTR (arg->unwind_link, _Unwind_GetIP) (ctx);
 41   if (++arg->cnt == arg->size)
 42     return _URC_END_OF_STACK;
 43
 44   /* %ebp is DWARF2 register 5 on IA-32.  */
 45   arg->lastebp
 46     = (void *) UNWIND_LINK_PTR (arg->unwind_link, _Unwind_GetGR) (ctx, 5);
 47   arg->lastesp
 48     = (void *) UNWIND_LINK_PTR (arg->unwind_link, _Unwind_GetCFA) (ctx);
 49   return _URC_NO_REASON;
 50 }
 51
 52
 53 /* This is a global variable set at program start time.  It marks the
 54    highest used stack address.  */
 55 extern void *__libc_stack_end;
 56
 57
 58 /* This is the stack layout we see with every stack frame
 59    if not compiled without frame pointer.
 60
 61             +-----------------+        +-----------------+
 62     %ebp -> | %ebp last frame--------> | %ebp last frame--->...
 63             |                 |        |                 |
 64             | return address  |        | return address  |
 65             +-----------------+        +-----------------+
 66
 67    First try as far to get as far as possible using
 68    _Unwind_Backtrace which handles -fomit-frame-pointer
 69    as well, but requires .eh_frame info.  Then fall back to
 70    walking the stack manually.  */
 71
 72 struct layout
 73 {
 74   struct layout *ebp;
 75   void *ret;
 76 };
 77
 78
 79 int
 80 __backtrace (void **array, int size)
 81 {
 82   struct trace_arg arg =
 83     {
 84      .array = array,
 85      .unwind_link = __libc_unwind_link_get (),
 86      .size = size,
 87      .cnt = -1,
 88     };
 89
 90   if (size <= 0 || arg.unwind_link == NULL)
 91     return 0;
 92
 93   UNWIND_LINK_PTR (arg.unwind_link, _Unwind_Backtrace)
 94     (backtrace_helper, &arg);
 95
 96   if (arg.cnt > 1 && arg.array[arg.cnt - 1] == NULL)
 97     --arg.cnt;
 98   else if (arg.cnt < size)
 99     {
100       struct layout *ebp = (struct layout *) arg.lastebp;
101
102       while (arg.cnt < size)
103 	{
104 	  /* Check for out of range.  */
105 	  if ((void *) ebp < arg.lastesp || (void *) ebp > __libc_stack_end
106 	      || ((long) ebp & 3))
107 	    break;
108
109 	  array[arg.cnt++] = ebp->ret;
110 	  ebp = ebp->ebp;
111 	}
112     }
113   return arg.cnt != -1 ? arg.cnt : 0;
114 }
115 weak_alias (__backtrace, backtrace)
116 libc_hidden_def (__backtrace)

ref:
誰在呼叫我?不同的backtrace實作說明好文章

2024年3月17日 星期日

【共鸣GK】OPM工作室 宫崎骏系列005-天空之城

OPM工作室 宫崎骏系列005-天空之城從淘寶【共鸣GK】預購, 20230514 訂購, 訂金 988rmb, 20240104 補款 2600rmb, 20240203 到住所, 20240204 拿到。20240317 開箱組好。

fig 5. 外箱與內箱, 20230515 訂購, 訂金 988rmb, 20240104 補款 2600rmb, 20240203 到住所, 20240204 拿到。20240317 開箱組好。


使用公主集運運回台灣, 淘寶賣家使用貨到付款, 寄到公主集運集貨倉由公主集運代付運費, 忘記運費多少, 大概是 59rmb, 寄回台灣運費 225 rmb, 25kg, 另外加上包稅的 50rmb, 再加上 988 + 2600 rmb 就是全部費用了。 被海關課進口稅/營業稅, 合計共 10%, 252 nt, 還好有包稅。

2600+988+59+225 = 3872 rmb





紙箱很大很重, 一般轎車的行李箱放不下, 總共3盒保麗龍, 真的很重, 分開成3個保麗龍盒才勉強搬得動。零件蠻多的, 花了大約2小時組好, 比較難的是小飛艇的翅膀, 一直裝不好, 最後只好上沒翅膀的版本, 小飛艇上的人物也是另外組裝, 要把手的部份對準小飛艇操縱桿, 其他部份零件算好裝。

所有配件: 巴魯 (パズー) + 希達 (シータ) + 朵拉 (ドーラ) + 小兵*4+飛艇 + 飛行器*4 + 城堡主體 + 金屬銘牌 + 神秘特典 (機器人) + 組裝說明書。

有附遙控器, 可以發出天空之城主題曲。
另外還有一個金幣和機器人, 整體高度和一個板凳差不多高。飛艇和主角群的滑翔翼艙做的很精緻。
最上層的森林我第一眼看到時覺得像是青花菜。



很喜歡天空之城這個作品, 從主題曲到動畫、漫畫都喜歡, 剛知道這個 gk 時, 就忍不住訂購, 等了 8 個月才等到, 真的好久, 不過開箱之後反而沒有很驚豔的感覺, 不知道是不是當時的衝動感過了。

以下影片展示了音樂播放, 本來以為那個遙控器是控制燈光, 原來是播放天空之城主題曲, gk 本身沒電池, 那條線是供應電源用, 是特規的線, 壞了就麻煩了。



這是我第3個 gk, 以後應該不會再買了, 太燒錢也很佔空間。

2024年3月10日 星期日

[漫畫] 化物語

the 1st edition: 20230725
the 2nd edition: 20240310
the 3rd edition: 20240402
購買的是漫畫電子版本, 在「樂天kobo」購買, 在 kobo 台灣網站購買也可以, 只是沒有折扣, 樂天kobo 提供不少折扣, 有一次我買3本, 花了 111, 化物語電子版定價 55。在我買的當下 (20230721), 出了 1 ~ 18, 目前紙本出到 20。

kobo 的電子書有3個地方可以購入, 折扣各有不同:
https://www.kobo.com/tw/zh (刷卡屬於國外刷卡)
https://www.rakuten.com.tw 星期二固定有 75 折, 不固定有 30 折價券, 買 55 漫畫很划算。
https://24h.pchome.com.tw/books/region/DJBQ 每月6號固定有 75 折

會考慮買電子版本是因為實體版有些集數收不到 (在 202403 這個時間點, 之前的集數都再刷了), 在「樂天kobo」購買的體驗很好。

kobo 有提供檔案下載的功能, 請參考從您的 Kobo 帳戶中下載書籍,以匯出至另一裝置或應用程式, 這個下載不是你想像中的那種下載 (簡單來說就算下載了也無法直接觀看電子書內容), 請仔細閱讀其中內容, 免得誤會「下載」的意思, 電子書很難搞的。請參考「[問題] Pubu的Adobe DRM書籍於閱讀器上閱讀?」就知道電子書的難搞程度。目前我只買 kobo 的電子書, 其他平台不考慮, 很重要的原因就是「可下載」。另外一間是 pubu, 也是有提供可下載的電子書。我每次鼓吹這點的重要性, 卻很少得到回應, 甚至有人不在意, 似乎要等到帳號或是服務倒閉才能體會這件事情的嚴重性, 看別人的例子失去那些買的電子書好像都沒感覺, 也助長那些不提供下載的電子書城。

另外會買的原因很大一部份是因為主角的單車是 moulton dobule pylon, 這部單車要價 19999 英鎊, 對, 我沒寫錯, 大概 60 萬台幣。就是下圖的那台單車, 曾經希望在 35 歲時能購入這台單車。





之前也有一些特殊單車出現在漫畫上, 例如: birdy, 但是一般都會是單車漫畫, 這種特殊車種一般不會出現在非單車漫畫上, 所以才引起我想購買的興趣, 而也因為其實我沒有特別喜愛這部漫畫, 才選擇便宜的電子書版本。不過畫風是我喜歡的風格, 故事看下去也還可以, 就是有廢話太多和很牽強矛盾的硬凹。如果看小說的話, 不知道是不是會有很多廢話的那種, 像倪匡的衛斯理就屬於這種, 故事明明很簡短, 但就是硬加上很多文字, 讓整個篇幅看起來很多; Stephen Edwin King 也是屬於這種。

回到化物語, 化物語敘事手法很亂, 不是倒敘也不是正序, 是亂序。要理清整個時間序會花點腦筋, 作者頭腦一定很好, 難怪可以解 fig 1 那個方程式。小說不知道, 貓班長在最後才敘述, 然後又再加上第二次現在時間點的貓班長。也許這樣交錯的時間序列看起來比較不單調, 但覺得有點過頭了, 因為整個故事很長, 要特別去記才能理清整個時間序。
fig 1. 可怕的數學題目

漫畫亂序版 漫畫版的怪異故事有 (按照時間順序排列)
  1. 重蟹
  2. 蝸牛
  3. 神原駿河
  4. 千石撫子
  5. 姬絲秀忒·雅賽蘿拉莉昂·刃下心
  6. 貓班長 I
  7. 貓班長 II
  1. 姬絲秀忒·雅賽蘿拉莉昂·刃下心
  2. 貓班長 I
  3. 重蟹
  4. 蝸牛
  5. 神原駿河
  6. 千石撫子
  7. 貓班長 II


另外中文將「怪異」直接翻譯感覺有點奇怪, 雖然勉強知道意思, 但中文不會用這個詞, 應該是用類似「鬼怪、妖怪」之類的詞彙。

化物語有一些情節前後矛盾, 感覺很硬凹, 閱讀起來很不和諧, 描述故事時, 順道列出。比喻的話, 如果化物語是偵探推理漫畫, 讀者可能會覺得有很多破綻, 或是自圓其說的感覺。

[姬絲秀忒·雅賽蘿拉莉昂·刃下心]
fig 2 姬絲秀忒·雅賽蘿拉莉昂·刃下心
姬絲秀忒·雅賽蘿拉莉昂·刃下心是吸血鬼, 被攻擊的人砍斷4肢, 求人救她, 需要吸那個人的血, 大概只有主角才願意這麼做吧, 一般人早閃了。但之後為了讓主角回復人類, 卻說自己本來就是來這裡找葬身之地, 本來就是來尋死, 要讓主角殺死她, 那妳當初就死一死不就沒後來的事情了嗎? 覺得這裡凹很大, 整個看過去總覺得很矛盾。

視力好這點剛好找到一個不合理之處:



主角以為是忍野, 不過實際上是羽川, 視力不是很好嗎? 而且還是全盛時期的吸血鬼能力, 在蝸牛篇還刻意用視力好來誤導讀者有關戰場原看不到 894 那段, 這下子矛盾了吧!

另外鬼物語紀錄了「姬絲秀忒·雅賽蘿拉莉昂·刃下心」如何被怪異攻擊, 只剩下 1/4 個身體, 攻擊他們的鬼怪到底是什麼? 漫畫沒這段, 鬼物語有出動畫。看了鬼物語動畫之後很失望, 以為是什麼厲害的妖怪大戰, 結果只是正常的自然現象, 把異常的現象排除, 就自然消除這個黑暗現象, 迷路的蝸牛不再迷路就是一個異常現象。鬼物語大部份內容都在廢話。

[貓班長 I]
fig 3. 怪異貓
這邊也是有一些矛盾硬凹之處, 在羽川被繼父打了之後, 說出:「爸爸, 不可以打女孩子的臉。」這樣有很奇怪嗎? 忍野咩咩竟然說這樣的回答我也想打她, 感覺只是為了讓羽川看起來真的很怪而硬凹。

而且後面有說明, 繼父會打她完全是因為被人控制的原因, 才不是因為羽川表現優秀過頭的關係。

另外這裡有一幕讓我非常感動, 羽川就算被貓貓附身, 也還是很在意阿良良木的安危, 阿良良木一通求救簡訊立刻讓 black 羽川前來救他。這是愛情還是友情?



[重蟹]

此篇女主角是戰場原, 名字很奇怪, 我搞了很久才確認這是名字, 一直在想提到戰場是什麼意思。個性很差的女主角, 超級令人厭惡的個性, 故事有暗示是故意讓人討厭的 (看起來很成功讓人討厭)。當然故事也有描述好的那面, 但差勁的地方實在太強烈, 蓋過那些優點了。

運用螃蟹當妖怪還蠻特別, 反正就是等價交換, 用體重換一個願望, 最後後悔再換回來。向主角告白, 成為男女朋友, 阿良良木應該是有病, 不選班長選這個。班長都要讓你柔胸部了。

[蝸牛篇]

蝸牛篇的廢話很多, 整個故事覺得很冗長、無趣, 一堆廢話很煩, 可能要把它當小說看會好些。本篇重點是戰場原看不見蝸牛, 還配合演戲, 這邊也覺得很牽強, 看不到就說看不到就好, 用可能是自己的問題這理由來說實在太牽強, 感覺是想要營造特殊的劇情, 讓讀者之後有個驚喜, 身為讀者, 我有一種被耍的感覺, 就這樣, 其他就是這些廢話了。另外894突然咬人感覺很不搭, 好像為了怪異而怪異, 不這麼做, 好像無法說明他是一個怪異。

也佩服作者的那些考究, 不知道是看了哪些書才能寫出那些淵源, 寫小說的人要多方涉獵很多知識, 寫出來的東西才精彩。

最討厭的就這篇, 894 人物畫風和劇情都不愛, 還特別長, 描述個迷路妖怪的故事, 再說一次很冗長很無聊, 如果你愛那些廢話文字或是典故, 也許會喜歡這篇。

[神原駿河]

這邊作者企圖用猴掌來混肴讀者, 最後點出這不是猴掌, 是另外一種妖怪。這篇結局完成願望和避免付出代價的手法蠻巧妙, 也比較沒什麼矛盾之處, 整個看來很協調。

神原駿河的設定也很令人喜愛, 是個外向的少女。

[千石撫子]

撫子那個沒有揭露誰是施咒的人也覺得有點沒有完滿。

阿良良木想去救那個施咒的人真的是令我覺得意外, 這個設定已經不是爛好人, 是聖人了吧!

[貓班長 II]

班長的戲份很多, 怪異貓再次襲來, 羽川翼再度變身為貓貓 (不是那個貓貓), 這次還多了一隻老虎, 要殺死阿良良木的女友 - 戰場原, 雖然我討厭戰場原, 但也不會希望她被殺, 反正之後也是快樂結局。

不過羽川翼也是因為某些原因, 才吸引了這些妖魔鬼怪, 幕後的影武者竟然是姬絲秀忒·雅賽蘿拉莉昂·刃下心的眷屬 (前一任), 他利用了羽川翼的負面情緒, 搞出了很多怪異 (妖魔鬼怪), 也才有了這一系列的化物語故事。

之後剪了頭髮, 可愛的辮子沒了, 眼鏡也拿下來, 似乎變成另外一個人, 認不出來是班長, 還我辮子眼鏡班長。

電子版本比紙本慢出很多, 最後一集 22 我在 20240222 才買到, 紙本 2023/11/23 出版, 差了很久。

另外東立的電子版漲價, 有非常高的漲幅, 之後應該不會購入了, 之前因為價格便宜, 還願意購入一些有點興趣的, 要是調漲之後的價格, 紙本、電子應該都不會購入。我自己不太能接受電子版和紙本書價差不大 (台灣漫畫家的作品也許還會購入電子版), 可能會導致我重新從二手市場找書, 或是不買一些娛樂型的書籍。

化物語則維持 55。

不過目前想看的野球太保沒有漲價, 還是 55, 但是七龍珠彩色版比克大魔王從定價是 165, 這個漲很多, 因為之前已經買了其他篇 (定價 105), 只好忍痛一起收, 目前還剩下少年篇 1-8 電子版還沒收。

結果野球太保 20240320 從 55 漲到 80, 這個漲幅實在太扯, 紙本打完折是 85, 這樣的對比我就不想買電子版了, 野球太保還差 58 ~ 71, 還要把電子書不能當二手書賣的成本也算進去, 80 實在太貴, 東立可以發個聲明, 到底為什麼會漲這麼多, 也許還能讓讀者接受。
fig 20. 電子版 80, 紙本版 70, 20240402 的價錢
fig 20 列出了野球太保紙本比電子版還便宜, 也許有人可以接受, 不過電子書沒有實體印刷、沒有擁有權、無法二手賣出, 這些成本不反應在書價說不過去吧! 有句話說:「做生意的人不要好處全拿。」總要給消費者一些回饋, 更不要說沒有裡封、折頁、目錄、和落後紙本書的出版時間。當然我沒算進運費, 不過運費不能算進書價應該沒什麼問題。

202404 東立又繼續再漲一波, 真的搞不懂這個操作。真想知道東立漲價之後的銷售情形。美食獵人列入這次的漲價, 本來還在考慮 55 元是不是要購買, 漲價後的 80 就完全不會考慮了。之後只能挑真的想要才買, 而不是便宜也可以買買看的心態去購買了。覺得是讀者和出版社雙輸的商業策略。

ref: