@@ -453,7 +453,206 @@ Dump of assembler code for function phase_4:
453453 0x000000000040105d <+81>: add $0x18,%rsp
454454 0x0000000000401061 <+85>: ret
455455End of assembler dump.
456+
457+ (gdb) disassemble
458+ Dump of assembler code for function func4:
459+ => 0x0000000000400fce <+0>: sub $0x8,%rsp
460+ 0x0000000000400fd2 <+4>: mov %edx,%eax
461+ 0x0000000000400fd4 <+6>: sub %esi,%eax
462+ 0x0000000000400fd6 <+8>: mov %eax,%ecx
463+ 0x0000000000400fd8 <+10>: shr $0x1f,%ecx
464+ 0x0000000000400fdb <+13>: add %ecx,%eax
465+ 0x0000000000400fdd <+15>: sar $1,%eax
466+ 0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx
467+ 0x0000000000400fe2 <+20>: cmp %edi,%ecx
468+ 0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
469+ 0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx
470+ 0x0000000000400fe9 <+27>: call 0x400fce <func4>
471+ 0x0000000000400fee <+32>: add %eax,%eax
472+ 0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
473+ 0x0000000000400ff2 <+36>: mov $0x0,%eax
474+ 0x0000000000400ff7 <+41>: cmp %edi,%ecx
475+ 0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
476+ 0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
477+ 0x0000000000400ffe <+48>: call 0x400fce <func4>
478+ 0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
479+ 0x0000000000401007 <+57>: add $0x8,%rsp
480+ 0x000000000040100b <+61>: ret
481+ End of assembler dump.
482+ ```
483+
484+ ### :crab: 解題策略
485+ 這個 phase 包含遞迴函數 `func4`,實作二分搜尋邏輯。需要理解遞迴呼叫和返回值的計算。
486+
487+ ### :crab: 解析步驟
488+
489+ 1. 理解輸入格式
490+ 和 Phase 3 類似,使用 `sscanf` 讀取兩個整數。
491+
492+ 2. func4 的參數
493+ 從 phase_4 呼叫 func4 時:
494+ ```assembly
495+ mov 0x8(%rsp),%edi # 第 1 參數 = 第一個輸入
496+ mov $0x0,%esi # 第 2 參數 = 0
497+ mov $0xe,%edx # 第 3 參數 = 14
498+ call func4
499+ ```
500+
501+ 3. func4 的邏輯結構
502+ **計算中點(Binary Search 的核心):**
503+ ```assembly
504+ mov %edx,%eax # %eax = high (14)
505+ sub %esi,%eax # %eax = high - low
506+ mov %eax,%ecx
507+ shr $0x1f,%ecx # 處理符號位(這裡是 0)
508+ add %ecx,%eax
509+ sar $1,%eax # 除以 2
510+ lea (%rax,%rsi,1),%ecx # %ecx = mid = (low + high) / 2
511+ ```
512+
513+ 這段計算:`mid = (low + high) / 2`
514+
515+ **三路分支判斷:**
516+ 分支 1:mid > target(搜尋左半邊)
517+ ```assembly
518+ cmp %edi,%ecx # 比較 mid 和 target
519+ jle 0x400ff2 # 如果 mid <= target,跳到分支 2
520+ lea -0x1(%rcx),%edx # high = mid - 1
521+ call func4 # 遞迴:func4(target, low, mid-1)
522+ add %eax,%eax # 返回值 *= 2
523+ jmp 0x401007 # 返回
524+ ```
525+
526+ 分支 2:mid == target(找到目標)
527+ ```assembly
528+ mov $0x0,%eax # 返回值 = 0
529+ cmp %edi,%ecx # 比較 mid 和 target
530+ jge 0x401007 # 如果 mid >= target,返回
531+ ```
532+
533+ 分支 3:mid < target(搜尋右半邊)
534+ ```assembly
535+ lea 0x1(%rcx),%esi # low = mid + 1
536+ call func4 # 遞迴:func4(target, mid+1, high)
537+ lea 0x1(%rax,%rax,1),%eax # 返回值 = 返回值 * 2 + 1
538+ ```
539+
540+ 4. 追蹤遞迴過程
541+ **使用 `si` 進入遞迴:**
542+ ```bash
543+ (gdb) break func4
544+ (gdb) run
545+ # 輸入前面的答案...
546+ 1 0 # 測試答案
547+
548+ (gdb) si # 用 si 而不是 ni!
549+ (gdb) backtrace # 查看遞迴深度
550+ (gdb) info registers # 查看參數變化
551+ ```
552+
553+ **自動追蹤遞迴呼叫:**
554+ ```bash
555+ (gdb) break func4
556+ (gdb) commands
557+ > silent
558+ > printf "func4(%d, %d, %d), mid=%d\n", $edi, $esi, $edx, ($esi+$edx)/2
559+ > continue
560+ > end
561+ ```
562+
563+ 5. 推導有效答案
564+ **關鍵觀察:** 只有當輸入等於某次計算的 mid 值時,才會走分支 2,返回 0。
565+
566+ 初始呼叫:`func4(target, 0, 14)`
567+
568+ **可能的 mid 值:**
569+ ```
570+ 第 1 層:mid = (0 + 14) / 2 = 7
571+ 第 2 層:mid = (0 + 6) / 2 = 3 或 mid = (8 + 14) / 2 = 11
572+ 第 3 層:mid = (0 + 2) / 2 = 1 或 mid = (4 + 6) / 2 = 5
573+ 第 4 層:mid = (0 + 0) / 2 = 0 或 mid = (2 + 2) / 2 = 2
574+ ```
575+
576+ **但只有在「自然分裂」路徑上的 mid 會返回 0:**
577+
578+ 從 14 開始不斷除以 2:
579+ ```
580+ 14 / 2 = 7
581+ 7 / 2 = 3
582+ 3 / 2 = 1
583+ 1 / 2 = 0
584+ ```
585+
586+ → **第一個輸入只能是:0, 1, 3, 7**
587+
588+ 6. 驗證答案
589+
590+ **測試不同輸入:**
591+ ```bash
592+ # 有效答案
593+ 0 0 ✅
594+ 1 0 ✅
595+ 3 0 ✅
596+ 7 0 ✅
597+
598+ # 無效答案(會經過分支 3,返回值非 0)
599+ 2 0 ❌
600+ 5 0 ❌
601+ ```
602+
603+ **驗證過程(以輸入 1 為例):**
604+ ```
605+ 第 1 次:func4(1, 0, 14) → mid=7, 7>1 → 分支 1 → func4(1, 0, 6)
606+ 第 2 次:func4(1, 0, 6) → mid=3, 3>1 → 分支 1 → func4(1, 0, 2)
607+ 第 3 次:func4(1, 0, 2) → mid=1, 1==1 → 分支 2 → 返回 0
608+ 回到第 2 次:返回值 = 0 * 2 = 0
609+ 回到第 1 次:返回值 = 0 * 2 = 0
456610```
457611
458- ## x86-64 assembly cheat sheet
612+ **答案(4 選 1)**
613+ ```
614+ 0 0
615+ 1 0 ← 我選這組
616+ 3 0
617+ 7 0
618+ ```
619+
620+ 第二個數字必須是 0(phase_4 會檢查)。
621+
622+ **學習重點**
623+ - **理解遞迴函數的分析方法**
624+ - 找 base case(終止條件)
625+ - 找 recursive case(遞迴呼叫)
626+ - 追蹤參數如何改變
627+
628+ - **使用 gdb 追蹤遞迴**
629+ - `si` 進入函數(vs `ni` 跳過函數)
630+ - `backtrace` 查看呼叫堆疊
631+ - 觀察每層遞迴的參數變化
632+
633+ - **二分搜尋的組語實作**
634+ - 計算中點:`(low + high) / 2`
635+ - 三路分支:小於、等於、大於
636+ - 遞迴縮小搜尋範圍
637+
638+ - **返回值的計算規律**
639+ - 分支 2(找到):返回 0
640+ - 分支 1(左半):返回值 * 2
641+ - 分支 3(右半):返回值 * 2 + 1
642+
643+ **重要 GDB 指令補充**
644+ ```bash
645+ backtrace (bt) # 顯示函數呼叫堆疊
646+ info registers # 顯示所有暫存器值
647+ info registers rdi rsi # 只顯示特定暫存器
648+ finish # 執行完當前函數返回上一層
649+ ```
650+
651+ **額外觀察**
652+ 這個 phase 展示了:
653+ - 遞迴函數在組語中的樣子
654+ - 如何通過返回值編碼不同的路徑
655+ - 為什麼某些看似合理的輸入會失敗(不在二分搜尋的「自然路徑」上)
656+
657+ ## :whale: x86-64 assembly cheat sheet
459658- https://web.stanford.edu/class/cs107/resources/x86-64-reference.pdf
0 commit comments