Skip to content

Commit 4b822da

Browse files
committed
feat: update phase 4 note in bomb-lab article
1 parent cb07b75 commit 4b822da

1 file changed

Lines changed: 200 additions & 1 deletion

File tree

docs/notes/CSAPP/bomb-lab.md

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,206 @@ Dump of assembler code for function phase_4:
453453
0x000000000040105d <+81>: add $0x18,%rsp
454454
0x0000000000401061 <+85>: ret
455455
End 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

Comments
 (0)