अपने स्वयं के कार्यक्रम का गतिशील विश्लेषणः डिबगर, निशान और स्थिर दावों की पुष्टि
Type: Build
Languages: Python, C, arm64 assembly, lldb command language
Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 35 (evidence, confidence, and unknowns)
Time: ~130 minutes
सीखने के लक्ष्य
- स्पष्ट करें कि लिनक्स पर ऑपरेटिंग सिस्टम से डिबगर की क्या आवश्यकता है (
ptrace) और macOS (मेक टास्क पोर्ट और अपवाद पोर्ट) पर, और क्यों एक सैंडबॉक्स शेल एक डिबगगी लॉन्च नहीं कर सकता है। - नाम और पते की सूची के द्वारा सॉफ्टवेयर ब्रेकपॉइंट सेट करें, हार्डवेयर वॉचपॉइंट सेट करें, और प्रोसेसर द्वारा दोनों पर लगाए गए सीमाओं को पढ़ें।
- एक स्टॉप हुक के साथ पायथन से बैच मोड में lldb ड्राइव करें जो प्रत्येक स्टॉप पर रजिस्टर, मेमोरी और फ्रेम चर रिकॉर्ड करता है, और एक टाइमलाइन में ट्रांसक्रिप्ट को वापस पार्स करता है।
- उस समयरेखा के खिलाफ पाठ 32, 33, 34 और 41 से स्थैतिक दावे की जांच करेंः तर्क रजिस्टर, स्ट्रक्चरल फील्ड ऑफसेट, जो किनारे एक सशर्त शाखा ले ली, और जो कॉल हुई।
- एक के खिलाफ एक ही योजना चलाएँ
-O2निर्माण, पढ़ें ब्रेकपॉइंट्स जो हल और कभी नहीं हिट, और क्या है और क्या साबित नहीं करता है कहना. - एक कोर फ़ाइल को सहेजें और लोड करें, और नाम दें कि स्टैक-स्टाइल कोर क्या छोड़ देता है।
- जीडीबी और एलडीबी कमांड सेट का नक्शा बनाएं, और स्थान
strace,ltrace,bpftrace,dtraceऔरrrअपने संस्करणों और लाइसेंस के साथ सही ऑपरेटिंग सिस्टम पर। - macOS कोड हस्ताक्षर तथ्य बताओ कि lldb आप खुद बनाया है एक बाइनरी डिबग करने के लिए अनुमति दी, और दो शर्तों के तहत यह इनकार करता है।
समस्या
पाठ 41 पढ़िए struct accountDWARF से और कहा balance4. पाठ 33 नियंत्रण प्रवाह ग्राफ बनायाclassifyऔर कहा कि पहली शाखा को जाता है return -1पाठ 32 में कहा गया है कि पहले दो तर्क में आते हैंx0और x1उन सभी वाक्य बाइट्स से प्राप्त हुए थे. उनमें से कोई भी अवलोकन नहीं किया गया था.
एक एजेंट फिर उसी कार्यक्रम को पढ़ता है और रिपोर्ट करता है "मुख्य कॉल"bump,classifyऔर sum_balances" प्रतीक तालिका में सभी तीनों को सूचीबद्ध किया गया है, इसलिए वाक्य सुरक्षित लग रहा है। आप कार्यक्रम का निर्माण पर-O2, प्रत्येक फ़ंक्शन पर एक ब्रेकपॉइंट सेट करें, और इसे चलाएं. प्रक्रिया इसकी चार पंक्तियों और आउटपुट प्रिंट करता है. प्रत्येक ब्रेकपॉइंट रिपोर्टhit count = 0. .
एजेंट स्रोत के बारे में गलत नहीं था, यह निर्माण के बारे में गलत था कि यह पूछा गया था. संकलक इनलाइन सभी तीन कॉल और तहsum_balances(rows, 3)इस मशीन पर इस बाइनरी के लिए पता करने का एकमात्र तरीका है एक डिबगर के तहत इसे चलाने और देखने के लिए है.
यह सबक एक ऐसा रिश्ता बनाता है जो देखने को बनाता है।account.c, सूची को पढ़ता है, एक आईएलडीबी सत्र की योजना बनाता है, इसे चलाता है, और स्टॉप के अनुसार प्रत्येक स्थैतिक दावे को दर्जा देता है। दर्जा तीन परिणामों के होते हैं। confirmedइसका मतलब है कि इस दौड़ के लिए दावा सही साबित होने के लिए एक स्टॉप।refutedइसका मतलब है कि एक स्टॉप ने इसे गलत साबित किया।not-observedपाठ 35 फिर हर फैसला को एक के रूप में संग्रहीत करता हैobservedअधिकार के साथ रिकॉर्ड controlled-replay. .
एक डिबगर तीन सिस्टम सेवाएं और एक तालिका है
डिबगर उस प्रक्रिया के लिए चार चीजें करता है जिसके पास कोड नहीं है। यह प्रक्रिया को रोकता है, इसके रजिस्टर को पढ़ता है और लिखता है, अपनी मेमोरी को पढ़ता है और लिखता है, और प्रक्रिया को फिर से रोकता है, यह बताया जाता है। कर्नेल इनमें से प्रत्येक को सेवा के रूप में प्रदान करता है, और सेवाएं ऑपरेटिंग सिस्टम द्वारा भिन्न होती हैं।
लिनक्स पर सेवा है ptrace(2). मैनुअल पृष्ठ में कहा गया है कि यह "एक साधन प्रदान करता है जिसके द्वारा एक प्रक्रिया (ट्रेसर) एक अन्य प्रक्रिया के निष्पादन का निरीक्षण और नियंत्रण कर सकती है। "PTRACE_TRACEMEएक बच्चे को यह घोषणा करने देता है कि उसके माता-पिता उसे ट्रैक करेंगे,PTRACE_ATTACHएक अनूठी प्रक्रिया लेता है, PTRACE_PEEKDATAपढ़ता है "एक शब्द पता पर पता पता लगाने के लिए, ट्रैक की स्मृति में",PTRACE_GETREGSरजिस्टर पढ़ता है, और PTRACE_SINGLESTEPएक निर्देश चलाता है. यम मोड्युल सीमा संलग्नः /proc/sys/kernel/yama/ptrace_scope1 का मतलब है "लक्ष्य प्रक्रिया को कॉल करने वाले के वंशज होना चाहिए", 2 का मतलब है कि केवल एक प्रशासक संलग्न कर सकता है, और 3 का मतलब है कि कोई भी नहीं कर सकता है।
macOS पर ptraceApple के मैनुअल सूची PT_TRACE_ME,PT_ATTACHEXC,PT_STEP,PT_CONTINUEऔर PT_KILL, और यह कहते हैं कि के साथ PT_ATTACHEXC"बच्चे से संकेत माता पिता के लिए जाते हैं Mach अपवाद के रूप में। " स्मृति और रजिस्टर नहीं गुजरते।ptraceबिल्कुल नहीं. आईएलडीबी के debugserverकॉलtask_for_pid()प्रक्रिया के लिए एक मैच कार्य बंदरगाह प्राप्त करने के लिए, तो task_set_exception_ports()ताकि ब्रेकपॉइंट अपवाद संदेश के रूप में आते हैं. सिस्टम डेमोनtaskgatedयह तय करता है कि कौन बुला सकता है task_for_pid. .
आप इस पाठ में बनाए गए एक प्रतिलेख में उस डिजाइन की लागत देख सकते हैं। सत्र का खोल एक सीटबेल्ट रेत बॉक्स के नीचे चलता है। वही योजना जो रेत बॉक्स के बाहर 11 स्टॉप उत्पन्न करती है, उसके अंदर यह उत्पन्न करती हैः
(lldb) run
error: process exited with status -1 (attach failed ((os/kern) invalid address))यह प्रक्रिया बनाई गई थी और फिर इसे अपनाया नहीं जा सका। त्रुटि डिबगर के अधिकारों के बारे में है, और यह कार्यक्रम के बारे में कुछ नहीं कहता है। हर्नस इसे लॉन्च विफलता के रूप में रिकॉर्ड करता है, शून्य रुके हुए और कोई फैसला नहीं।
तालिका ब्रेकपॉइंट तालिका है. डिबगर याद करता है कि निर्देश बाइट किस पते पर बदल दिया है, ताकि यह मूल को वापस रख सकता है जब आप जारी रखते हैं, और यह प्रति स्थान हिट गिनता है। breakpoint listउस मेज को प्रिंट करता है, और हर रन के अंत में हर्नस इसे पढ़ता है।
ब्रेकपॉइंट्सः एक बाइट या एक शब्द की जगह
एक सॉफ्टवेयर ब्रेकपॉइंट एक निर्देश है जो प्रोसेसर पर फंसता है, निर्देश के पहले बाइट्स पर लिखा है जिसे आप रोकना चाहते हैं। x86-64 पर यह हैINT3, ऑपकोड CC, एक बाइट लंबा, जिसे इंटेल मैनुअल "डिबग अपवाद हैंडलर को कॉल करने के लिए अभिप्रेत" के रूप में वर्णित करता है।BRK #imm16, एक चार बाइट का निर्देश जिसका अपवाद "ईसी मान 0x3C का उपयोग करके ESR_ELx में अपवाद रिकॉर्ड करता है।BRKनिर्देश स्वयं, यही कारण है कि एक स्टॉप प्रतिस्थापित निर्देश का पता रिपोर्ट करता है और उसके बाद का नहीं।
lldb नाम, फ़ाइल और पंक्ति, या पते द्वारा ब्रेकपॉइंट सेट करता है। नाम से यह प्रोग्रू को छोड़ देता हैः
(lldb) breakpoint set -n bump
Breakpoint 1: where = account-O0`bump + 12 at account.c:13:19, address = 0x00000001000004bcbump + 12चौथा आदेश है। इससे पहले के तीन हैंsub sp, sp, #0x10,str x0, [sp, #0x8]और str w1, [sp, #0x4], फ्रेम सेटअप जो DWARF लाइन तालिका समारोह की शुरुआती लाइन को जिम्मेदार ठहराता है।x0और x1अभी भी तर्क बनाए रखें, क्योंकि प्रोग्यू केवल उन्हें कॉपी किया।
पते के अनुसार, Ildb को यह जानना होगा कि पते का कौन सा मॉड्यूल है।breakpoint set -a 0x100000504प्रक्रिया अस्तित्व में से पहले एक लोड पता है, इसके पीछे कोई अनुभाग नहीं है, और एक पहले परीक्षण में यह रह गया unresolvedपूरे दौरे के दौरान hit count = 0. हर्नस का उपयोग करने वाला फॉर्म मॉड्यूल का नाम हैः
(lldb) breakpoint set -a 0x100000504 -s account-O0
Breakpoint 4: address = 0x0000000100000504के साथ-sसंख्या एक फ़ाइल पता अंदर है account-O0, lldb लोड स्लाइड जोड़ता है, और दौड़ के बाद स्थान पढ़ता है resolved, hit count = 2. पाठ 33 सूची फ़ाइल पते देता है, तो यह फॉर्म है कि एक गतिशील स्टॉप के लिए एक स्थैतिक सूची जोड़ता है.
शाखा को हाथ से डिकोड करें
classifyपर-O0यह शब्द है 36f800a8पर0x1000004f0, जो llvm-objdumptbz w8, #0x1f, 0x100000504. इसे Arm A64 कोडिंग से डिकोड करेंTBZ. .
- बिट 31 है
b5, बिट संख्या के उच्च बिट. यह 0 है. - 30 से 25 बिट्स हैं
011011, परीक्षण और शाखा समूह. बिट 24 0 है, जो चयन करता हैTBZऔर नहींTBNZ. . - बिट्स 23 से 19 तक हैं
b40, बिट संख्या के कम पांच बिट्सः11111= 31। के साथb5= 0 परीक्षण बिट 31 है, एक 32-बिट मूल्य के संकेत बिट. - बिट्स 18 से 5 हैं
imm1400000000000101= 5. शाखा का ऑफसेटimm14 * 4= 20 =0x14, तो लक्ष्य है0x1000004f0 + 0x14 = 0x100000504. . - 4 से 0 तक के बिट्स
Rt01000= 8, तो रजिस्टर हैw8. .
w8पहले एक निर्देश लोड किया गया था ldr w8, [x8, #0x4], जो है a->balance4.TBZशाखा जब बिट शून्य है। शाखा जब संकेत बिट स्पष्ट है लिया जाता है, जो है balance >= 0स्रोत ने लिखा हैif (a->balance < 0) return -1;, तो संकलक स्थिति को उलट दियाः लिया किनारा skip करता है return -1ब्लॉक, और गिरावट से पार करने के लिए 0x1000004f4इस प्रकार, हर्नस में होने वाला स्थैतिक दावा एक परिप्रेक्ष्य के साथ एक किनारा दावा हैः जब लिया जाता हैa->balance >= 0दूसरी शाखा की लिफ्ट भी इसी तरह काम करती है।subs w8, w8, #0xc8गणना balance - 200और b.le 0x100000524जब यह अधिकतम शून्य है, जो हैbalance <= 200. .
घड़ी की चौकी हार्डवेयर और कुछ ही हैं
एक वॉचपॉइंट प्रक्रिया को रोकता है जब किसी पते पर मेमोरी तक पहुंच जाती है। यह एक प्रतिस्थापित निर्देश नहीं हो सकता है, क्योंकि कहीं भी कोई भी निर्देश पहुंच कर सकता है। इसलिए प्रोसेसर पते की तुलना करने वाले एक छोटे सेट के साथ होते हैं। x86-64 पर वे डिबग रजिस्टर हैंःDR0पारDR3"अधिकतर 4 ब्रेकपॉइंट के पते" रखें, तथा DR7प्रत्येक को सक्षम करता है और इसकी स्थिति (कार्य, लिखें, या पढ़ें-लिखें) और इसकी लंबाई (1, 2, 4, या 8 बाइट्स) निर्धारित करता है।DBGWVR<n>_EL1, जो "वॉचपॉइंट मिलान के लिए इस्तेमाल किए गए डेटा वर्चुअल पता रखता है", और DBGWCR<n>_EL1नियंत्रण के लिए, और हाथ मैनुअल कहता है "एक कार्यान्वयन 2-64 निगरानी बिंदुओं के बीच शामिल कर सकते हैं।
आईएलडीबी ने इस संख्या की सूचना दी है:
(lldb) watchpoint set variable a->balance
Watchpoint created: Watchpoint 1: addr = 0x16fdfe00c size = 4 state = enabled type = m
(lldb) watchpoint list
Number of supported hardware watchpoints: 4इस मशीन पर चार।0x16fdfe00cहै x0 + 4 bumpरुकना प्लस DWARF ऑफसेट balance. .size = 4int. .type = mजब स्लॉट खत्म हो जाते हैं lldb अगले वॉचपॉइंट से इनकार करता है, और क्योंकि एक क्षेत्र जो संरेखित नहीं है या दो की शक्ति नहीं है दो स्लॉट ले सकता है, आप सेट कर सकते हैं की संख्या वॉचपॉइंट चार से कम हो सकता है.
डेटा वॉचपॉइंट एक्सेस के बाद रिपोर्ट करते हैं। इंटेल मैनुअल में कहा गया है कि "डेटा ब्रेकपॉइंट द्वारा उत्पन्न डिबग अपवाद उत्पन्न होने से पहले मेमोरी लिखने वाला एक निर्देश मूल डेटा को ओवरराइट करता है", और lldb दोनों को प्रिंट करने के लिए अपने स्वयं के कैश में पुराने मान को रखता हैः
Watchpoint 1 hit:
old value: 250
new value: 255
* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
frame #0: 0x00000001000004d0 account-O0`bump(a=0x000000016fdfe008, by=5) at account.c:14:12रुकना है 0x1000004d0, निर्देश के बाद str w8, [x9, #0x4]पर0x1000004cc. एक विचित्रता है कि आप उत्पादन का विश्लेषण करने से पहले जानने लायक है.watchpoint set, इलडीबी फिंगरप्रिंट Watchpoint 1 hit:एक खाली पुराना मूल्य के साथ और new value: 250यह प्रारंभिक रीडिंग है, और हर्नस एक पुराने मूल्य के बिना हिट को अनदेखा करता है।
कदम, हुक रोकना और बैच मोड
lldb के आदेश व्याकरण है <noun> <verb> [-options] [arguments]. कदम करने वाले क्रियाएँ हैं thread step-in(s), thread step-over(n), thread step-inst(si), thread step-inst-over(ni), तथा thread step-out(finish ).thread until 12एक रेखा तक चला जाता है। thread return <expr>एक मूल्य के साथ जल्दी लौटता है। हर्नस कदम नहीं करता है। यह नियोजित पते पर रुकता है और एक स्टॉप हुक को पढ़ने देता हैः
target stop-hook add -o "register read x0 x1 pc" -o "memory read -s4 -fx -c24 $x0" -o "frame variable -P 1"-oदोहराया जा सकता है, और आदेश क्रम में हर स्टॉप पर चल रहे हैं।register readदो तर्क रजिस्टर और कार्यक्रम काउंटर का नाम। memory read -s4 -fx -c24 $x024 चार बाइट शब्द छपाई हैक्स में पता से x0, जो 96 बाइट्स है, तीन struct accountपंक्तियाँ।frame variable -P 1तर्क और स्थानीय छपाई और एक स्तर पर संकेतकों का विस्तार, तो aअपने पांच क्षेत्रों को दिखाता है।
बैच मोड lldb -b. . . मैनुअल में कहा गया है--batchआदेशों को चलाता है -s,-S,-oऔर -Oऔर फिर छोड़ देता है, और अगर एक आदेश बंद "सिग्नल या दुर्घटना के कारण, डिबगर इंटरैक्टिव प्रॉम्प्ट पर वापस आ जाएगा। " हर्नस एक फ़ाइल में योजना लिखता है और यह पारित करता है के साथ -s. एक सेटिंग मायने रखता हैः settings set interpreter.stop-command-source-on-error false. इसके बिना, पहला continueप्रक्रिया समाप्त होने के बाद पूरी फ़ाइल समाप्त हो जाती है, और breakpoint listकभी नहीं चलाता है।error: Process must be launched.और आगे बढ़ता है।
आउटपुट आकार के बारे में दो तथ्य तय करते हैं कि पार्सर कैसे काम करता है। पहला, हुक आउटपुट हुक के चलने के रूप में प्रिंट किया जाता है, और Process N stoppedउसके साथ ब्लॉक frame #0लाइन बाद में मुद्रित की जाती है। इसलिए पार्सर रजिस्टर, मेमोरी और चर लाइनों को एक लंबित टुकड़े में एकत्र करता है और अगले टुकड़े से चिपकाता है frame #0एक टुकड़ा जिसका कार्यक्रम काउंटर में हैdyldपर_dyld_startकार्यक्रम चलाने से पहले प्रवेश रोक है, और नहीं frame #0दूसरा, एक ब्रेकपॉइंट के साथ--auto-continueइस हर्नेस के पहले संस्करण में ऑटो-कंटीन्यू का इस्तेमाल किया गया और कुछ भी रिकॉर्ड नहीं किया गया। योजना में सादे ब्रेकपॉइंट और एक निश्चित बजट का उपयोग किया गया है continueआदेशों के बजाय.
योजना सूची से आती है
Plan.commands()कमांड फ़ाइल को बनाएँ llvm-objdump -dबाइनरी पाठ यह चला जाएगा.account-O0:
breakpoint set -n bump
breakpoint set -n classify
breakpoint set -n sum_balances
breakpoint set -a 0x100000504 -s account-O0
breakpoint set -a 0x1000004f4 -s account-O0
breakpoint set -a 0x100000524 -s account-O0
breakpoint set -a 0x100000514 -s account-O0
target stop-hook add -o "register read x0 x1 pc" -o "memory read -s4 -fx -c24 $x0" -o "frame variable -P 1"
run
watchpoint set variable a->balance
watchpoint list
continue
...
breakpoint list
watchpoint list -vतीन नाम ब्रेकपॉइंट फ़ंक्शन प्रविष्टियाँ हैं। चार पता ब्रेकपॉइंट लक्ष्य और प्रत्येक सशर्त शाखा का परिणाम हैं जो Listing.conditional_branches()classifytbzपर0x1000004f0और b.leपर0x100000510. पहली स्टॉप पर निगरानी बिंदु निर्धारित किया गया है, जो योजना में होने की उम्मीद है bump. 16 continueबजट कार्यक्रम के बारे में ज्ञान नहीं है यह एक छत है। यह एक बजट है जो एक कार्यक्रम के बारे में ज्ञान नहीं है।
दावा उसी इनपुट से निर्मित होते हैं claims_for():
| Id | Kind | Claim | Source |
|---|---|---|---|
arg-registers | argument-register | at every entry the first arguments arrive in x0 and x1 | lesson 32 and AAPCS64 |
field-offsets | field-offset | struct account is 32 bytes with kind at 0, balance at 4, code at 8, rate at 16, label at 24 | lesson 41 and the DWARF dump |
branch-1 | branch-taken | tbz at 0x1000004f0 goes to 0x100000504 when a->balance >= 0, otherwise to 0x1000004f4 | lesson 33 edges and the lesson 34 lift |
branch-2 | branch-taken | b.le at 0x100000510 goes to 0x100000524 when a->balance <= 200, otherwise to 0x100000514 | lesson 33 edges and the lesson 34 lift |
calls | call-edge | main calls bump once, classify three times, sum_balances once | source text and nm |
store-once | store | bump writes a->balance once and the new value is the old value plus by | lesson 34 pseudocode |
stride | struct-stride | list[i].balance sits at list + 32*i + 4 | lesson 41 and the psABI aggregate rule |
output | behaviour | the program prints class 0 1 -1 and sum 315 | source arithmetic |
क्षेत्र ऑफसेट से आते हैं struct_layout(), जो पढ़ता है DW_TAG_structure_typeके साथDW_AT_byte_size (0x20)और पांच DW_TAG_memberउनके साथ प्रविष्टियाँ DW_AT_data_member_locationयोजना कभी भी संख्या 4 टाइप नहीं करता है यह पढ़ता है।
-0 पर दौड़ः 11 रुके
पहला रुकावट है bumpब्रेकपॉइंट. हुक ने स्टॉप ब्लॉक से पहले यह प्रिंट कियाः
x0 = 0x000000016fdfe008
x1 = 0x0000000000000005
pc = 0x00000001000004bc account-O0`bump + 12 at account.c:13:19
0x16fdfe008: 0x00000073 0x000000fa 0x00000009 0x00000000
0x16fdfe018: 0x00000000 0x3ff40000 0x00000719 0x00000001
0x16fdfe028: 0x0000006c 0xffffffd8 0x00000003 0x00000000
0x16fdfe038: 0x00000000 0x40000000 0x00000721 0x00000001
(account *) a = 0x000000016fdfe008 {
kind = 's'
balance = 250
code = 9
rate = 1.25
label = 0x0000000100000719 "savings"
}
(int) by = 5DWARF ऑफसेट के खिलाफ पहली पंक्ति के आठ शब्दों को पढ़ें। ऑफसेट 0 पर शब्द 0 है 0x73, जो है 's', kindबाइट, इसके बाद तीन पैडिंग बाइट्स। शब्द 1 ऑफसेट 4 पर है 0xfa= 250, balance. शब्द 2 ऑफसेट 8 पर है 0x9,code, ए shortशब्द 3 ऑफसेट 12 में 8-बाइट संरेखित से पहले पैडिंग हैdouble. शब्द 4 और 5 ऑफसेट 16 पर हैं 0x3ff4000000000000, जो IEEE-754 कोडिंग 1.25. शब्द 6 और 7 ऑफसेट 24 में हैं0x100000719, label__TEXT. अगले आठ शब्द से शुरू होते हैं0x6c= 'l'और 0xffffffd8= -40: तीसरी पंक्ति, loan, 32 बाइट्स बाद में. हर संख्या के साथ सहमत हैframe variable, तो _check_layout()रिकॉर्ड field-offsetsजैसे confirmedइस स्टॉप पर, और यह 11 स्टॉप में से प्रत्येक पर फिर से ऐसा करता है जहां x0एक संरचनात्मक सूचक रखता है।
x0बराबर aऔर x1बराबर by, तो arg-registersइस प्रविष्टि में पुष्टि की गई है। हर्नस केवल घोषित तर्कों की तुलना करता है।classifyरुक जाता हैx1अभी भी पढ़ता है 0x5, से एक अवशेष bumpऔर classifyएक रजिस्टर जिसमें कोई तर्क नहीं रहता है शोर है, और पाठ 32 दावा इसके बारे में कुछ नहीं कहता है।
दूसरा रुकावट है घड़ी बिंदु, पुराना मूल्य 250, नया मूल्य 255, by= 5। पूरे रन में एक हिट, तो store-onceतो तीन सक्रियण आता हैclassify, प्रत्येक में अपना प्रवेश द्वार और एक या दो किनारे बंद होते हैंः
| Stop | Address | Stop reason | What it tests |
|---|---|---|---|
| 2 | 0x1000004e8 | breakpoint 2.1 | entry, a->balance = 100, x0 = a |
| 3 | 0x100000504 | breakpoint 4.1 | tbz taken, 100 >= 0, branch-1 confirmed |
| 4 | 0x100000524 | breakpoint 6.1 | b.le taken, 100 <= 200, branch-2 confirmed |
| 5 | 0x1000004e8 | breakpoint 2.1 | entry, a->balance = 255 |
| 6 | 0x100000504 | breakpoint 4.1 | tbz taken, 255 >= 0 |
| 7 | 0x100000514 | breakpoint 7.1 | b.le fell through, 255 > 200 |
| 8 | 0x1000004e8 | breakpoint 2.1 | entry, a->balance = -40 |
| 9 | 0x1000004f4 | breakpoint 5.1 | tbz fell through, -40 < 0 |
| 10 | 0x100000544 | breakpoint 3.1 | sum_balances entry, x1 = 3 |
_check_branches()सक्रियण में स्टॉप को समूह करता है, प्रविष्टि पर संतुलन पढ़ता है, और प्रत्येक किनारे स्टॉप के लिए उत्तराधिकारी की गणना करता है उठाया predicate भविष्यवाणी करता है। पांच किनारे स्टॉप, पांच समझौतेः branch-1तीन बार पुष्टि की,branch-2दो बार. स्टॉप 5 संतुलन को 255 के रूप में दिखाता है, जो कि मूल्य है कि घड़ी बिंदु लिखा देखा है. रन एक कार्यक्रम है और सबूत कनेक्ट करता है.
10 के स्टॉप पर हुक ने 24 शब्द पढ़ लिएlist. ._check_stride()शब्द 1, शब्द 9, और शब्द 17, जो ऑफसेट हैं 4, 36, और 68: 100, 255, और -40 लेता है। यह संतुलन के साथ प्रत्येक तुलना करता है।classifyएक ही पते पर देखा, उन्हें 315 तक जोड़ता है, और राशि की तुलना sum 315लाइन प्रोग्राम मुद्रित किया गया है. फिर प्रक्रिया स्थिति 0 के साथ बाहर निकलता है और breakpoint listनामों के लिए 1, 3, 1 और चार किनारों के लिए 2, 1, 1, 1 के हिट गिनती दिखाता है। सभी आठ दावे समाप्तconfirmed, 25 रिकॉर्ड पीछे, 23 एक स्टॉप और दो समाप्त रन के लिए बाध्य के साथ.
-O2 पर दौड़ेंः शून्य रुकावटें
-O2एक अलग कार्यक्रम है।llvm-objdump -dmainचार है blनिर्देश, सभी के लिए printf. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .mov w8, #-0x1,mov w8, #0x1, शून्य से पार xzrऔर mov w8, #0x13b, जो 315 है. संकलक संकलन समय में प्रत्येक परिणाम की गणना की। nmअभी भी सूची _bump,_classifyऔर _sum_balances, क्योंकि वे बाहरी कार्यों हैं और अन्य वस्तुओं को कॉल करने के लिए लाइन से बाहर की प्रतियां मौजूद होनी चाहिए।DW_TAG_subprogramसभी चार कार्यों के लिए प्रविष्टियाँ और शून्य DW_TAG_inlined_subroutineप्रविष्टियों, तो यहां तक कि डिबग जानकारी इनलाइनिंग रिकॉर्ड नहीं करता है।
इस निर्माण के लिए योजना में तीन नाम ब्रेकपॉइंट और कोई किनारे ब्रेकपॉइंट नहीं हैं। classifyपर-O2छह निर्देश है, ldr,cmp,cset,cmp,csinv,retकगार से दावा करता है कि-O0सूचीबद्ध करने का इस बाइनरी में कोई पता नहीं है, और हर्नस उन्हें पार नहीं करता है। एक किनारे के बारे में दावा उस बिल्ड का है जिसका सूचीबद्ध किनारे पर है।
दौड़:
(lldb) breakpoint set -n bump
Breakpoint 1: where = account-O2`bump at account.c:13:16, address = 0x0000000100000460
(lldb) run
sizeof 32 align 8
offsets 0 4 8 16 24
class 0 1 -1
sum 315
Process 48444 exited with status = 0 (0x00000000)
(lldb) watchpoint set variable a->balance
error: Command requires a process which is currently stopped.
(lldb) breakpoint list
1: name = 'bump', locations = 1, resolved = 1, hit count = 0प्रत्येक ब्रेकपॉइंट हल किया गया। पता मौजूद है, प्रतीक मौजूद है, और lldb एक लिखा BRKवहाँ. हत्याओं की संख्या शून्य है क्योंकि कोई निर्देश प्राप्त करने के लिए कभी पता तक पहुँच गया. फैसलेः
| Claim | Verdict | Record |
|---|---|---|
calls | refuted | after a full run with exit status 0, entry stops bump 0, classify 0, sum_balances 0 |
output | confirmed | the four lines and exit status 0 |
arg-registers | not-observed | no stop tested it, hit counts all 0 |
field-offsets | not-observed | no stop tested it |
store-once | not-observed | no stop in bump, so the watchpoint was never set |
stride | not-observed | no stop in sum_balances |
यह दावा था किmainइस कार्यक्रम में तीन कार्यों को बुलाता है। इस निर्माण के लिए और इस रन के लिए यह नहीं करता है, और हर्नस की जाँच की कि रन पूरा हो गया, क्योंकि एक शून्य से बाहर निकलने से पहले हिट गिनती का मतलब होगा केवल कि रन अभी तक कॉल तक नहीं पहुंचा था। दावों में नहीं कहा गया कि कार्य मृत कोड हैं, और रन में यह नहीं कहा गया कि either: वे निर्यात किए जाते हैं और एक अन्य वस्तु उन्हें कॉल कर सकती है। अन्य चार दावों की पुष्टि या खंडन नहीं की गई है। संरचना अभी भी हैbalanceइस निर्माण में ऑफसेट 4 पर, और -O2DWARF ऐसा कहता है, लेकिन इस दौड़ में कोई रोक नहीं स्मृति पढ़ने के लिए यह दिखाने के लिए। not-observedऔर पाठ 35 अज्ञात को खुला रखता है जब तक कि एक दौड़ इसे नहीं देखता।
कोर डंप
एक कोर फ़ाइल प्रक्रिया की स्मृति और डिस्क पर लिखे गए रजिस्टर है, ताकि डिबगर प्रोग्राम के गायब होने के बाद एक बंद प्रोग्राम पढ़ सकता है।core(5)कहते हैं "एक छोटी संख्या में संकेत जो प्रक्रिया के असामान्य समापन का कारण बनते हैं, प्रक्रिया के मूल राज्य का रिकॉर्ड डिस्क पर लिखा जाता है",/cores/core.pid"जब तक समाप्त प्रक्रिया निर्देशिका में लिखने की अनुमति थी, और निर्देशिका मौजूद था। " इस मशीन पर।sysctlरिपोर्ट kern.coredump: 1और kern.corefile: /cores/core.%P,/coresमोड के साथ मौजूद है drwxrwxr-t root admin, और खोल की ulimit -c0 है। कोई भी नाभिक कोर लिखा जाता है जब तक आप सीमा बढ़ा ulimit -c unlimited, या पूरे सिस्टम के साथ launchctl limit core unlimited. .
आईएलडीबी बिना किसी दुर्घटना के मांग पर एक कोर लिखता है।bumpटूटने का बिंदुः
(lldb) process save-core --style stack account-O0-stack.core
Saving 49152 bytes of data for memory region at 0x16fdf4000
Modified-memory or stack-memory only corefile created. This corefile may
not show library/framework/app binaries on a different system, or when
those binaries have been updated/modified. Copies are not included
in this corefile. Use --style full to include all process memory.फ़ाइल 53,248 बाइट्स थी. इसे लोड करने के लिए इसके बगल में बाइनरी की आवश्यकता हैः
(lldb) target create "./account-O0" --core "account-O0-stack.core"
(lldb) bt
* thread #1, stop reason = ESR_EC_BRK_AARCH64 (fault address: 0x0)
* frame #0: 0x00000001000004bc account-O0`bump(a=0x000000016fdfe008, by=5) at account.c:13:19
frame #1: 0x0000000100000640 account-O0`main at account.c:40:5
frame #2: 0x000000019a949d54 dyld`start + 7184
(lldb) frame variable *a
(account) *a = (kind = 's', balance = 250, code = 9, rate = 1.25, label = "")
(lldb) frame variable a->label
(const char *) a->label = 0x0000000100000719 ""बैकट्रैक और पूर्णांक क्षेत्र बरकरार हैं, क्योंकि वे स्टैक पर रहते हैं कि कोर बचाया। labelएक खाली स्ट्रिंग के रूप में प्रिंट करता है, जिसमें उसका पॉइंटर बरकरार है।0x100000719सही है; बाइट्स यह इंगित करने के लिए शाब्दिक हैं "savings"__TEXTनिर्देशांक के खंड में, और स्टैक शैली के कोर में उस खंड को शामिल नहीं किया गया है। पॉइंटर एक अवलोकन है। स्ट्रिंग उपलब्ध नहीं है, और lldb इसे आविष्कार नहीं करता है। --style fullइसमें सभी प्रक्रिया मेमोरी शामिल है, और शोध मशीन पर इस 34 KB कार्यक्रम का पूर्ण कोर 2.1 GB था, क्योंकि प्रक्रिया साझा कैश का नक्शा बनाती है। रोकने का कारण पढ़ता है ESR_EC_BRK_AARCH64: कोर ने रिकॉर्ड किया BRKअपवाद है कि ब्रेकपॉइंट उठाया।
लिनक्स पर नाभिक कोर के अनुसार लिखता है/proc/sys/kernel/core_pattern, के साथ एक टेम्पलेट%pपीआईडी के लिए, %eकमांड नाम के लिए, %sसंकेत के लिए, और %tसमय के लिए, और एक प्रमुख |सिस्टम वितरण उस पाइप का उपयोग करने के लिए एक कार्यक्रम के लिए डिंपsystemd-coredumpऔर coredumpctl list,info,dumpऔर debugजो यह संग्रहीत किया गया था उसे वापस ले लो।gdb prog coreऔर lldb -c core progदोनों परिणाम खोलें।
जीडीबी और आईएलडीबी पक्ष-पक्ष
दो डिबगर्स अलग-अलग शब्दों के साथ एक ही काम करते हैं। COMMAND_MAPहर्नेस में 35 पंक्तियाँ हैं, जो python3 code/main.py commands. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
| Task | gdb | lldb | lldb alias |
|---|---|---|---|
| break at a function | break bump | breakpoint set -n bump | b bump |
| break at an address in one module | break *0x1000004f0 | breakpoint set -a 0x1000004f0 -s account-O0 | |
| run commands at every stop | define hook-stop ... end | target stop-hook add -o '...' | |
| list breakpoints with hit counts | info breakpoints | breakpoint list | br l |
| watch a variable for writes | watch a->balance | watchpoint set variable a->balance | wa s v a->balance |
| list watchpoints | info watchpoints | watchpoint list -v | wa l -v |
| read one register | p $x0 | register read x0 | |
| read memory as words | x/8xw 0x16fdfe008 | memory read -s4 -fx -c8 0x16fdfe008 | x -s4 -fx -c8 0x16fdfe008 |
| show arguments | info args | frame variable --no-locals | |
| step one instruction | stepi | thread step-inst | si |
| run until the function returns | finish | thread step-out | finish |
| write a core file | gcore core | process save-core --style stack core | |
| open a core file | gdb account-O0 core | lldb -c core account-O0 |
आईएलडीबी p का एक उपनाम हैdwim-print, जो एक सादा चर के माध्यम से प्रिंट करता है frame variableपथ और वापस गिर जाता हैexpressionऔर कुछ भी के लिए.vहै frame variable.frame variableएक हर्नस मेंः यह DWARF स्थानों को पढ़ता है और कभी भी डिबगगी में कोड चलाता है।
2026-10-14 के संस्करण और लाइसेंस। lldb में LLVM संस्करण संख्या है, और वर्तमान llvm.org संस्करण 2026-10-06 की LLVM 23.1.3 है, जो LLVM अपवादों के साथ Apache लाइसेंस 2.0 के तहत है। होमब्रे इसे अलग सूत्र के रूप में भेजता है lldbइस मशीन जहाज पर कमांड लाइन उपकरणlldb-1703.0.236.21, एप्पल की अपनी संख्या, एक एम्बेडेड पायथन 3.9.6 के साथ, और एप्पल के lldb पहले से ही डिबग करने के लिए हस्ताक्षरित है। GDB 18.1 GPL-3.0 या बाद के तहत 2026-09-25 पर जारी किया गया था। इसकी रिलीज नोट्स विंडोज, .gdb_index, AArch64 FPMR, और पायथन gdb.Corefileपरिवर्तन और कोई डार्विन प्रविष्टि नहीं। GDB में 18.1 तक एप्पल सिलिकॉन प्रक्रियाओं के लिए कोई मूल समर्थन नहीं हैः अक्टूबर 2025 में 15 पैच "मूल एआरएच 64-डार्विन समर्थन" श्रृंखला पोस्ट की गई थी, और फरवरी 2026 में एक रखरखावकर्ता ने कॉपीराइट असाइनमेंट और बीएफडी पैच के माध्यम से इसे लैंड करने का मार्ग निर्धारित किया। होमब्रे gdbसूत्र में arm64 बोतलें हैं और एक नकली के साथ निर्माण करता है --target=x86_64-apple-darwin20, और उन्हें जोड़ा गया खींच अनुरोध कहते हैं "यह संभव नहीं है GDB के माध्यम से aarch64-Darwin द्विआधारी बग करने के लिए। macOS पर किसी भी गैर-Apple डिबगर भी एक स्व-समर्थित प्रमाण पत्र और com.apple.security.cs.debuggerअधिकार, याtask_for_pid"(os/kern) विफलता (0x5) " के साथ विफलता। इस पाठ्यक्रम में gdb की जगह लिनक्स है, जहां डेबियन स्थिर जहाजों arm64 के लिए gdb 16.3 है, और जहां rrजीवन।
ट्रैकिंगः प्रोग्राम ने कर्नल से क्या पूछा
डिबगर प्रोग्राम की स्थिति को स्टॉप पर दिखाता है। एक ट्रैकर प्रोग्राम के सिस्टम पर प्रभाव को लंबे समय तक बंद किए बिना दिखाता है। उपकरण प्रति ऑपरेटिंग सिस्टम हैं।
| Tool | OS | Version | Licence | What it records |
|---|---|---|---|---|
strace | Linux | 7.2, 2026-08-18 | LGPL-2.1-or-later | system calls with arguments and return values |
ltrace | Linux | 0.7.3 last tarball, git snapshots in Debian | GPL-2.0 | dynamic library calls |
bpftrace | Linux | v0.27.0, 2026-09-10 | Apache-2.0 | kernel and user probes through eBPF |
dtrace and dtruss | macOS | Sun D 1.19 in macOS 26.2 | CDDL, from Sun's OpenSolaris | D probes; dtruss prints system calls |
dtrussअपने मैनुअल में कहा गया है कि "क्योंकि यह DTrace का उपयोग करता है, केवल रूट विशेषाधिकार वाले उपयोगकर्ता इस कमांड को चला सकते हैं।"dtrace(1)यह जोड़ता है कि "डिफ़ॉल्ट सिस्टम इंटीग्रटी प्रोटेक्शन सेटिंग्स के साथ, डी प्रोग्राम को कर्नेल एड्रेस मानों या कर्नेल मेमोरी सामग्री तक पहुंचने की अनुमति नहीं है" और कि विनाशकारी कार्यों को SIP के तहत अनुमति नहीं है। sudo dtruss ./account-O0इस मशीन पर इस सबक में नहीं चलाया गया था, क्योंकि हर्नस बिना जड़ चलाता है, और सबक रिकॉर्ड करता है कि के रूप में सत्यापित नहीं किया गया है। एक निशान हर्नस से एक अलग सवाल का जवाब देता हैः "क्या यह रन समस्या एकwriteप्रत्येक के लिए printfरेखा" एक निशान सवाल है, "क्या किया x1 पर 5 रखोbump" एक डिबगर प्रश्न है. दोनों प्रति रन हैं.
रिकॉर्ड और रीप्ले
rrएक बार एक रन रिकॉर्ड करता है और इसे जीडीबी के तहत निर्धारणीय रूप से फिर से चलाता है, उल्टा निष्पादन के साथः आपके द्वारा परवाह किए जाने वाले मूल्य पर एक निगरानी बिंदु सेट करें और reverse-contलिखते समय पीछे की ओर चला जाता है। परियोजना खुद को "आप एक बार एक विफलता रिकॉर्ड करते हैं, फिर आप रिकॉर्डिंग को डिटर्मिस्टिक रूप से, जितनी बार चाहें डिबग करते हैं। " वर्तमान रिलीज 5.9.0 है 2025-02-13, MIT लाइसेंस के तहत BSD-2-Clause ब्लॉक के साथ। यह केवल लिनक्स है। शाखाओं की गणना करने के लिए हार्डवेयर प्रदर्शन काउंटर की आवश्यकता होती है, इसलिए यह इंटेल नेहेलेम और बाद में, कुछ एएमडी जेन कोर और "कुछ एआरएच 64 माइक्रोआर्किटेक्चर (जैसे) पर चलता है। ARM Neoverse N1 या एप्पल सिलिकॉन एम-सीरीज) " लिनक्स के तहत। एक आभासी मशीन के अंदर काउंटरों को वर्चुअलाइज़ किया जाना चाहिए, और परियोजना विकी में कहा गया है "मैक पर कोई अन्य वर्चुअलाइज़ेशन नहीं करता है", इसलिए इस मशीन पर डॉकर और कोलिमा इसे चला नहीं सकते। पेर्नोस्को एक वाणिज्यिक सेवा है जो आरआर रिकॉर्डिंग पर आधारित है।
जीडीबी के पास अपना स्वयं का सॉफ्टवेयर रिकॉर्डर है। record full200,000 निर्देशों की डिफ़ॉल्ट सीमा के साथ "प्रक्रिया निष्पादन का लॉग" रिकॉर्ड करता है, और record btraceइस मैकओएस मशीन पर, हर्नस का सहेजा गया प्रतिलेख रिकॉर्ड हैः एक ही बाइनरी पर एक ही योजना एक ही क्रम में एक ही स्टॉप का उत्पादन करती है, और फिक्स्चर उस क्रम को पिन करता है।
दौड़ने से क्या पता चलता है
एक रन एक बिल्ड पर एक इनपुट साबित करता है। सी मानक निर्दिष्ट व्यवहार को "व्यवहार, एक गैर-पोर्टेबल या गलत प्रोग्राम निर्माण या गलत डेटा के उपयोग पर, जिसके लिए यह दस्तावेज़ कोई आवश्यकता नहीं रखता है, जिसके लिए" परिभाषित करता है।int पर अलग-अलग परिणामों को कानूनी रूप से प्रिंट कर सकते हैं-O0और -O2. पाठ 36 यह percent_changeUBSan एक बंद प्रश्न में बदल देता हैः-fsanitize=signed-integer-overflow -fsanitize-trap=undefinedएक कारण के साथ एक रोक के रूप में आईएलडीबी में लैंड करने के लिए एक जाल निर्देश "कार्य करने के लिए" ओवरफ्लो बनाता है।
अनुकूलित कोड भी वह स्थानांतरित करता है जिसे आप देखना चाहते हैं। जीडीबी के मैनुअल में कहा गया है कि "अनुकूलनकर्ता ने आपके कोड को फिर से व्यवस्थित किया है; डिबगर आपको दिखाता है कि वास्तव में क्या है" और कि अस्तित्व से बाहर अनुकूलित चर एक है "जीडीबी कभी नहीं देखता है। जीडीबी इस तरह का एक मूल्य प्रिंट करता है जैसे <optimized out>, और इसका स्रोत उसे अलग करता है <unavailable>: अनुकूलित "प्रोग्राम में वास्तव में मौजूद नहीं है", अनुपलब्ध "प्रोग्राम में मौजूद है (मशीन स्तर पर), लेकिन GDB के पास इसे प्राप्त करने के लिए कोई साधन नहीं है।<variable not available>जब प्रोग्राम काउंटर चर की DWARF स्थान सूची की हर सीमा के बाहर है। एक स्थानीय पर एक वॉचपॉइंट जो एक रजिस्टर में रहता है कभी भी फायर नहीं करता है, क्योंकि एक रजिस्टर स्मृति नहीं है।-O2ऊपर चला गया चरम मामला दिखाता हैः पूरे समारोह को टाल दिया गया था, और एक निगरानी बिंदु की चुप्पी जो कभी सेट नहीं किया गया था कुछ भी साबित नहीं करता है।
इस प्रकार, हर्नस तीन प्रकार के रिकॉर्ड लिखता है और कभी भी चौथा नहीं।confirmedएक स्टॉप जो इसे परीक्षण किया और असहमत हैrefutedएक दावा कोई रोक नहीं कर सकता परीक्षण हैnot-observedपाठ 35 की लेजर पहले दो को स्वीकार करता है के रूप मेंobservedअधिकार के साथ रिकॉर्ड controlled-replay, और तीसरे को एक खुला अज्ञात के रूप में रखता है।
macOS पर केवल अपने स्वयं के बिल्ड डिबगिंग
एप्पल सिलिकॉन पर लिंकर आपके लिए संकेत देता है।ld(1)कहते हैं -adhoc_codesign: "Apple सिलिकॉन बाइनरी के लिए डिफ़ॉल्ट रूप से एड-होक कोड डिज़ाइन किया जाना है।" codesign -dvvv account-O0फिक्स्चर प्रिंट परः
Format=Mach-O thin (arm64)
CodeDirectory v=20400 size=387 flags=0x20002(adhoc,linker-signed) hashes=9+0 location=embedded
Signature=adhoc
TeamIdentifier=not set0x20002है CS_ADHOC(0x2) प्लस CS_LINKER_SIGNED(0x20000), "लिंकर द्वारा स्वचालित रूप से हस्ताक्षरित" Apple के कोड हस्ताक्षर हेडर में। codesign -d --entitlements -निष्पादित नाम के बाद कुछ भी प्रिंट नहीं करता हैः द्विआधारी कोई अधिकार नहीं है। lldb इसे लॉन्च किया, हर ब्रेकपॉइंट पर मारा, हार्डवेयर वॉचपॉइंट सेट किया, और एक कोर लिखा, के साथ DevToolsSecurity -statusरिपोर्ट "डेवलपर मोड वर्तमान में अक्षम है" और सिस्टम अखंडता संरक्षण सक्षम है।
दो अधिकारों का नियंत्रण है कि कौन क्या डिबग कर सकता है, और वे विपरीत पक्षों पर बैठे हैं। com.apple.security.cs.debuggerडिबगर के लिए हैः "एक बुल मान जो इंगित करता है कि क्या ऐप एक डिबगर है और अन्य प्रक्रियाओं से जुड़ सकता है या कार्य बंदरगाह प्राप्त कर सकता है। "debugserverइसका निजी रूप धारण करता है, तथाcodesign -d --entitlements -कमांड लाइन टूल कॉपी सूची पर com.apple.private.cs.debugger,com.apple.private.set-exception-port,com.apple.private.thread-set-stateऔर com.apple.private.logging.diagnostic. .com.apple.security.get-task-allowXcode इसे डिबग बिल्ड में इंजेक्ट करता है, निर्यात पर इसे हटा देता है, और नोटरीकरण एक बाइनरी को अस्वीकार करता है जो अभी भी इसे रखता है। एप्पल के इंजीनियर अस्वीकार के लिए नियम बताते हैंः कार्य बंदरगाह को तब मना कर दिया जाता है जब लक्ष्य में हार्ड रनटाइम सक्षम है और नहीं है।get-task-allow. एक सादा clangयदि आप कभी भी एक फिक्स्ड के साथ हस्ताक्षर करते हैंcodesign --options runtime, इसे फिर से हस्ताक्षर करेंcodesign -s - -f --entitlements get-task-allow.plistडिबगिंग से पहले।
तीन और तथ्य अनुभाग को बंद करते हैं। सबसे पहले, आप लॉगिन सत्र में पहली बार संवाद देख सकते हैं "डेवलपर टूल्स एक्सेस को डिबगिंग जारी रखने के लिए एक और प्रक्रिया का नियंत्रण लेने की आवश्यकता है; "sudo DevToolsSecurity -enableadminया _developerसमूहों. दूसरा, आप अपने स्वयं के कोड डिबग करने के लिए SIP को अक्षम नहीं करते हैं। Apple कहता है "आपको Xcode से ऐप चलाने और डिबग करने के लिए SIP को अक्षम करने की आवश्यकता नहीं है", और एकमात्र SIP घटक जो डिबगिंग को छूता है वह है csrutil enable --without debugतीसरा, इस सत्र में सीटबेल्ट रेत बॉक्स ब्लॉकtask_for_pidपूरी तरह से, और फिक्स्ड sandbox-O0.lldb.txtएक दौड़ जो शुरू नहीं कर सकती है एक पर्यावरण तथ्य है, और हर्नस इसे एक के रूप में रिपोर्ट करता है।
इस पाठ में सब कुछ आपके द्वारा संकलित किए गए कार्यक्रमों पर लागू होता है. वही आदेश किसी अन्य प्रक्रिया से संलग्न होंगे केवल यदि इसके मालिक ने सहमति व्यक्त की हो, और यहाँ कुछ भी नहीं सिखाता है कि इसे कैसे बदला जाए।
इसे बनाओ
डेमो चलाएँः
cd phases/13-tools-and-protocols/42-dynamic-analysis-of-your-own-program
python3 code/main.pyआउटपुट सहेजे गए प्रतिलेखन पर एक JSON रिपोर्ट है। प्रत्येक बिल्ड के लिए यह उनके रिज़ॉल्यूशन और हिट गिनती के साथ ब्रेकपॉइंट्स, हार्डवेयर वॉचपॉइंट गिनती, उसके रजिस्टर और तर्कों के साथ प्रत्येक स्टॉप, आउटपुट स्थिति, प्रोग्राम का आउटपुट, छपाई गई त्रुटियों को सूचीबद्ध करता है, और उनके फैसले और सबूत रिकॉर्ड के साथ दावे। यह मूल और फिर यात्रा जोड़ता है, पायथन एपीआई परिणाम, सैंडबॉक्स विफलता, और जो बाहरी उपकरण इस मशीन पर अनुपस्थित हैं.
अपेक्षित आकारः
{
"program": "account.c",
"variants": {
"O0": {
"binary": "account-O0",
"breakpoints": {"1": {"spec": "name=bump", "address": "0x1000004bc", "resolved": true, "hits": 1}, "...": "..."},
"hardwareWatchpoints": 4,
"watchpoints": {"1": {"address": "0x16fdfe00c", "size": 4, "type": "m", "hits": [[250, 255]], "hitCount": 1}},
"stops": [{"index": 0, "pc": "0x1000004bc", "function": "bump", "kind": "entry", "registers": {"x0": "0x16fdfe008", "x1": "0x5"}, "...": "..."}],
"exitStatus": 0,
"claims": [{"id": "arg-registers", "verdict": "confirmed", "confirmed": 5, "refuted": 0, "not-observed": 0, "...": "..."}],
"evidence": [{"claim": "arg-registers", "verdict": "confirmed", "stop": 0, "observation": "bump: x0 = 0x16fdfe008 and a = 0x000000016fdfe008, x1 = 0x5 and by = 5"}]
},
"O2": {"stops": [], "claims": [{"id": "calls", "verdict": "refuted", "...": "..."}], "...": "..."}
},
"core": {"savedAt": {"function": "bump", "pc": "0x1000004bc"}, "labelString": "", "...": "..."},
"types": {"python": "3.9.6", "size": 32, "...": "..."},
"sandbox": {"stops": 0, "errors": ["process exited with status -1 (attach failed ((os/kern) invalid address))"]},
"absentTools": ["bpftrace", "gdb", "llvm-objdump", "ltrace", "rr", "strace"]
}पढ़िए code/main.pyइस क्रम मेंः
Listingllvm-objdump -dकार्य और निर्देशों में पाठ, औरFunction.conditional_branches()शाखाओं को अपने लक्ष्यों और गिरावट के साथ पाता है।struct_layout()से संरचना और सदस्य प्रविष्टियों को पढ़ता हैllvm-dwarfdumpपाठ.lift_condition()एक सशर्त शाखा को एक प्रस्तावना में बदल देता है, पाठ 34 कदम के लिएtbz,cbz, और तुलना-और-शाखा रूपों।Plan.commands()सूची से lldb कमांड फ़ाइल लिखता है, औरPlan.lldb_command()बैच का आह्वान है।parse_transcript()आउटपुट क्रम में चलता है और बनाता हैStopरजिस्टर, मेमोरी, चर और वॉचपॉइंट मानों के साथ रिकॉर्ड, साथ ही ब्रेकपॉइंट तालिका और आउटपुट स्थिति।claims_for()यह आठ दावों का उल्लेख करता है, तथाevaluate()वे के साथ बंद करने के लिए बंद करने के लिए के साथ_check_*कार्य।rollup()रिकॉर्ड को प्रति दावा एक फैसला तक कम करता है, जिसमें पुष्टि से पहले खंडन किया जाता है।capture()औरcapture_extras()फिक्स्चर का उत्पादन किया, औरlive()एक नए निर्माण पर पूरे लूप को दोहराता है जबclangऔरlldbदौड़ सकता है।
चार और आदेश मौजूद हैं।python3 code/main.py plan O0एक निर्माण के लिए कमांड फ़ाइल प्रिंट करता है। python3 code/main.py commandsजीडीबी और एलएलडीबी तालिका को प्रिंट करता है। python3 code/main.py toolsरिपोर्टें जिन पर बाहरी उपकरण हैं PATH. .python3 code/main.py live O0संकलित करता है account.cएक अस्थायी निर्देशिका में, अपनी नई सूची से योजनाएं, चलाता है lldb, और उस रन के खिलाफ दावे को दर्जा देता है। एक सैंडबॉक्स के अंदर यह रिपोर्ट करता है कि lldb प्रक्रिया शुरू नहीं कर सका, त्रुटि के साथ, और 1 से बाहर निकलता है।
इसका प्रयोग करें
इस क्रम में अपने स्वयं के कार्यक्रम पर हार्नेस का उपयोग करें।
- इसे बनाओ
-gआप बाइनरी, उसके dSYM या उसके DWARF, औरllvm-objdump -dएक साथ पाठ. - आप दौड़ने से पहले क्लेम लिखें. प्रत्येक में एक फ़ंक्शन, एक रजिस्टर या एक पता या एक ऑफसेट, एक अपेक्षित मूल्य या किनारा, और वह पाठ या दस्तावेज़ से आया है। स्टॉप देखने के बाद आप एक क्लेम लिखते हैं, जो स्टॉप का वर्णन करता है और कुछ भी परीक्षण नहीं करता है।
- सूची से योजना उत्पन्न करें और इसे पढ़ें. इसमें प्रत्येक पते को सूची में एक पंक्ति का पता लगाना चाहिए। यदि किसी ब्रेकपॉइंट में ऐसी पंक्ति नहीं है, तो इसे हटा दें।
- बैच मोड में एक बार चलाएं और कमांड लाइन और डिबगर संस्करण के साथ प्रतिलिपि को उसके हेडर में रखें। प्रतिलिपि सबूत है। फैसले इससे व्युत्पन्न होते हैं, और कोई भी उन्हें पुनः प्राप्त कर सकता है।
- ग्रेड. हर पढ़ें
not-observedपुष्टिओं को पढ़ने से पहले रिकॉर्ड और इसका कारण बताएं। कारण आपको बताते हैं कि कौन से दावे को एक और इनपुट, एक और बिल्ड, या एक अन्य ब्रेकपॉइंट की आवश्यकता है। - एक ही दावे के साथ अनुकूलित बिल्ड चलाएं। शून्य हिट के साथ हल ब्रेकपॉइंट की उम्मीद करें जहां संकलक को इनलाइन किया गया था, और उन दावे को खुला रखें।
- पाठ 35 की पुस्तिका में पुष्टि और खंडन किए गए रिकॉर्ड को फ़ीड करें जैसे
observed, प्राधिकरणcontrolled-replayअनदेखीओं को अज्ञात के रूप में छोड़ दें, ब्रेकपॉइंट हिट गिनती संलग्न है।
इंटरैक्टिव लैब
लैब ए: हाथ से एक स्टॉप पढ़ें
codeनिर्देशिका, खुला पायथनः
cd phases/13-tools-and-protocols/42-dynamic-analysis-of-your-own-program/code
python3 -qदौड़ें:
from main import *
t = load_transcript("session-O0.lldb.txt")
s = t.stops[0]
hex(s.pc), s.function, s.reason, s.registers
[hex(w) for w in s.memory[:8]]
s.struct_pointer().fieldsदूसरी पंक्ति से पहले, लिखें कि आप 250 शब्द और 1.25.struct.unpack("<d", struct.pack("<II", s.memory[4], s.memory[5]))और तुलना करें. फिर देखोt.stops[1].watch_hitऔर t.stops[1].pc, और उस पते को खोजें account-O0.objdump.txt: यह दुकान के बाद निर्देश है।
लैब बीः शाखा और उसके उत्तराधिकारी
plan = make_plan("O0")
plan.branches()
classify = plan.listing.function("classify")
[lift_condition(classify, b, "a->balance")[0] for b in plan.branches()]
[(hex(s.pc), s.struct_pointer().field_int("balance")) for s in t.stops_in("classify")]प्रत्येक किनारे के स्टॉप को उसके प्रवेश स्टॉप से मेल खाने और सबूतों को देखने से पहले तय करने के लिए, जो उत्तराधिकारी भविष्यवाणी की है। फिर चलें [e.to_dict() for e in evaluate(plan, t, struct_layout(open("fixtures/account-O0.dwarfdump.txt").read(), "account")) if e.claim.startswith("branch")]और तुलना करें।
लैब सी: दौड़ जो कभी नहीं रुकती
o2 = load_transcript("session-O2.lldb.txt")
len(o2.stops), o2.exit_status, o2.hits()
o2.errors[:3]
make_plan("O2").branches()
rollup(evaluate(make_plan("O2"), o2, struct_layout(open("fixtures/account-O2.dwarfdump.txt").read(), "account")))प्रत्येक को समझाएँ not-observedअपने शब्दों में, तो खुला account-O2.objdump.txtऔर चार स्थिरांक में खोजें mainजो कॉल की जगह ले लिया. फिर गिनती.DW_TAG_inlined_subroutineमें account-O2.dwarfdump.txtके साथcount_tags(). .
लैब डीः सबूत तोड़
text = open("fixtures/session-O0.lldb.txt").read().replace("x1 = 0x0000000000000005", "x1 = 0x0000000000000006", 1)
broken = parse_transcript(text)
[e.to_dict() for e in evaluate(plan, broken, struct_layout(open("fixtures/account-O0.dwarfdump.txt").read(), "account")) if e.claim == "arg-registers"][:2]एक संपादित रजिस्टर पहले बदल जाता है arg-registersरिकॉर्ड करने के लिए refutedऔर इसके साथ रोलअप, जबकि अन्य चार प्रविष्टियों में रहने के लिए confirmed. फिर हटाने के लिए old value: 250निगरानी बिंदु से पहले लाइन रोकें और देखें store-onceगिरने के लिएnot-observedएक ग्रेडर जो असफल नहीं हो सकता है वह ग्रेडिंग नहीं है।
यदि आईएलडीबी आपके शेल में एक प्रक्रिया शुरू कर सकता है, तो python3 main.py live O0और python3 main.py live O2. स्टैक पते फिक्स्चर से अलग होगा.
अभ्यास प्रयोगशाला
लूप के बारे में दावा के साथ हार्नेस को बढ़ाएँ sum_balances. .
आवश्यकताएँः
- के पीछे की सीमा लूप खोजें
sum_balances-O0सूचीबद्धconditional_branches()और लूप हेडर पर एक ब्रेकपॉइंट जोड़ें. - एक दावा जोड़े जो शीर्षक निष्पादित करता है
count + 1समय के लिएcount= 3, जो पाठ 33 बैक किनारे प्लस आउटपुट परीक्षण है। - स्टॉप हुक या योजना को बढ़ाएं ताकि हुक हर हेडर स्टॉप पर लूप काउंटर के स्टैक स्लॉट को पढ़ सके, और एक लिखें
_check_loop()जो काउंटर मानों के क्रम को रिकॉर्ड करता है। - के साथ प्रतिलेख को पुनः प्राप्त करें
python3 main.py capture O0एक मशीन पर जहां एलएलडीबी लॉन्च कर सकते हैं, और फिर से सूट चला सकते हैं. योजना परीक्षण विफल हो जाएगा जब तक प्रतिबद्ध कमांड फ़ाइल नए योजना से मेल नहीं खाती है, जो कि उस परीक्षण का बिंदु है. - एक परीक्षण जोड़े जो प्रतिलेख में एक काउंटर मान को संपादित करता है और दावा के मोड़ को पुष्टि करता है
refuted. .
सफलता का अर्थ है कि रिपोर्ट में शीर्षक के क्रम में उनके काउंटर मानों के साथ रुकता है, और -O2रन नई दावा रखता है not-observedइस कारण से कि लूप वेक्टरलाइज्ड या फॉल्ट हो गया था।
पाठ कलाकृतियाँ
इस सबक में शामिल है outputs/skill-dynamic-analysis-of-your-own-program.md. इसका उपयोग आपके द्वारा बनाए गए प्रोग्राम, इसकी सूची और स्थिर दावे की सूची को एक सहेजे गए डिबगर ट्रांसक्रिप्ट और प्रत्येक दावे के लिए एक फैसला में बदलने के लिए करें, जिसमें हर फैसला का उत्पादन करने वाला स्टॉप और हर दावे के कारण जो अवलोकन नहीं किए गए थे। यह क्लाउड, कर्सर, कोडेक्स, ओपनक्लाव, हर्मेस या SKILL.md को पढ़ने वाले किसी भी एजेंट पर लागू होता है।
जाँचें
डेमो और निर्धारक सूट चलाएंः
cd phases/13-tools-and-protocols/42-dynamic-analysis-of-your-own-program
python3 code/main.py
python3 -m unittest discover -s code/tests -vसूट बिना आईएलडीबी चलाता है. यह एक बार में कैप्चर किए गए प्रतिलेखन को पढ़ता हैcode/fixtures/सत्यापन से साबित होना चाहिएः
- सूची पार्सर चार कार्यों को उनके रेंज और दो सशर्त शाखाओं के साथ पाता है
classifyपर-O0लक्ष्य और गिरावट के साथ tbz w8, #0x1fहैa->balance >= 0औरsubsऔरb.leहैa->balance <= 200mainपर-O0कॉलbump,classifyतीन बार, औरsum_balancesऔरmainपर-O2केवल कॉलprintf- x86-64
j<cc>मेनमोनिक सशर्त हैं औरjmpऔरcallqनहीं हैं - DWARF डंप में सदस्य 0, 4, 8, 16 और 24 पर 32 बाइट स्ट्रक्चर होता है
-O2DWARF में चार उप-प्रोग्राम और शून्य इनलाइन उप-रूटीन प्रविष्टियाँ हैं, औरnmअभी भी तीन कार्यों की सूची- योजना दोनों बिल्ड के लिए प्रतिबद्ध कमांड फ़ाइल के बराबर है, और ट्रांसक्रिप्ट हेडर इसे चलाया गया lldb कमांड रिकॉर्ड करता है
-O0ट्रांसक्रिप्ट में अपेक्षित कार्यों में ग्यारह स्टॉप, आउटपुट स्थिति 0 और चार आउटपुट लाइनें हैं- हुक आउटपुट को उसके बाद के स्टॉप से जोड़ा जाता है, के साथ
x1= 5, स्मृति से शुरू होता हैx0, और लेबल स्ट्रिंग - ब्रेकपॉइंट तालिका में रेज़ॉल्यूशन और हिट गिनती 1, 3, 1, 2, 1, 1, 1 है
- घड़ी बिंदु 4 बाइट्स है प्रकार में संशोधित
x0 + 4, पुराने मूल्य 250 और नए मूल्य 255 के साथ एक बार मारा, और चार हार्डवेयर स्लॉट रिपोर्ट किया जाता है -O2प्रतिलिपि शून्य रुके, शून्य हिट के साथ तीन हल ब्रेकपॉइंट, कोई वॉचपॉइंट नहीं है, और प्रति अप्रयुक्त एक त्रुटि हैcontinue- सैंडबॉक्स लॉन्च संलग्न त्रुटि के साथ किसी भी रोक से पहले विफल रहता है
- कोर रिंगट्रिप बैकट्रैक रखता है और
balance= 250 और पीछे स्ट्रिंग खो देता हैlabel - पायथन एपीआई पांच क्षेत्रों, आकार 32 पढ़ता है, और समारोह प्रक्रिया के बिना रेंज
- एक हाथ से लिखा x86-64 ट्रांसक्रिप्ट के साथ विश्लेषण
rdi,rsiऔरrip - प्रत्येक दावे की पुष्टि
-O05, 11, 3, 2, 1, 1, 1, 1 रिकॉर्ड के साथ - शाखा साक्ष्य मूल्य, स्थिति और किनारे का नाम देता है
- क्षेत्र-ऑफसेट सबूत DWARF लेआउट के माध्यम से शब्दों को डिक्रिप्ट करता है
- भंडार और कदम सबूत पुराने और नए मूल्य और तीन पंक्ति के संतुलन ले जाते हैं
-O2रन कॉल किनारों को खारिज करता है और अन्य दावों को अनदेखा छोड़ देता है- एक संपादित रजिस्टर मान को खारिज किया जाता है, एक हटाए गए वॉचपॉइंट हिट का पालन नहीं किया जाता है, और खारिज किया गया रोलअप जीतता है
- कमांड मैप में प्रत्येक पंक्ति के लिए gdb और lldb फॉर्म हैं, और हार्डवेयर तालिका में चार x86 डिबग रजिस्टर नाम हैं
- डेमो विफलता के बजाय अनुपस्थित उपकरणों की सूची देता है, और लाइव मोड नाम से गायब उपकरण की रिपोर्ट करता है
- आंकड़े डेटा दोनों सत्रों से आता है और साइट मॉड्यूल में एक ही डेटा शामिल है
उत्पादन विफलता मोड
| Failure | What the weak report says | What the harness must prove |
|---|---|---|
| Resolved breakpoint with zero hits | "the function runs" or "the function is dead" | a full run with exit status, hit counts, and the verdict refuted for the call edge only |
| Register read at the wrong stop | "x1 = 5 inside classify" | compare only declared arguments at the entry stop |
| Struct read without offsets | a column of hex words | every word mapped to a DW_AT_data_member_location and compared with frame variable |
| Branch graded from the mnemonic | "tbz means taken when negative" | the lifted predicate from the encoding and the stop at the successor |
| Watchpoint on a register-resident local | "no write happened" | the watchpoint was set on an address and was hit, or the record is not-observed |
| Auto-continue breakpoints | a clean transcript with no stops | plain breakpoints, a stop hook, and a continue budget |
| Address breakpoint without a module | unresolved, hit count = 0 | -s MODULE so the listing address resolves to a load address |
| First command error ends the batch | no breakpoint list at the end | interpreter.stop-command-source-on-error false |
| Stack-style core read as full memory | label = "" reported as the program's value | the core style, the pointer, and the segment it points into |
| Sandboxed shell | "lldb is broken" | attach failed ((os/kern) invalid address) recorded as an environment fact |
Hardened target without get-task-allow | "macOS forbids debugging" | the two conditions from Apple's rule, and a re-sign of your own binary |
| Transcript discarded after grading | verdicts with no source | the transcript file, its command line, and the debugger version |
परिचालन नियम
सूची से स्टॉप की योजना बनाएं, बैच मोड में एक बार चलाएं, प्रतिलेख रखें, और प्रत्येक स्टॉप पर हर स्थिर दावे को दर्जा दें जो इसे परीक्षण कर सकता है। केवल एक स्टॉप से पुष्टि या खंडन लिखें, हिट की गिनती और कारण के साथ अनदेखी लिखें, और कभी भी एक तय ब्रेकपॉइंट को रन के लिए खड़ा न होने दें। केवल आपके द्वारा बनाए गए बिल्ड को डिबग करें।
आगे पढ़ना
- LLDB Tutorial: command grammar, breakpoints, watchpoints, stop hooks
- lldb(1) manual page: --batch, --one-line, --source, --core
- GDB to LLDB Command Map
- LLDB Python Reference: SBTarget, SBFrame, SBValue, and the embedded interpreter
- LLDB Caveats: the Python build lldb links against
- LLVM 23.1.3 release
- GDB 18.1 release announcement, 2026-09-25
- GDB manual: Process Record and Replay
- GDB manual: Debugging Optimized Code
- GDB wiki: Permissions on Darwin, code signing a debugger
- gdb-patches: Initial AArch64-Darwin support series, October 2025
- Homebrew gdb formula pull request adding arm64 bottles
- ptrace(2), Linux manual page
- lldb debugserver MachTask.mm: task_for_pid and task_set_exception_ports
- Intel SDM Volume 2: INT n, INTO, INT3, INT1
- Intel SDM Volume 3: debug registers DR0 to DR7 (Debug, Branch Profile, TSC, and Resource Monitoring Features)
- Arm A64 ISA: BRK, Breakpoint instruction
- Arm A64 ISA: TBZ, Test bit and branch if zero
- Arm Architecture Reference Manual, D2.9 Watchpoint exceptions
- Apple ld(1): -adhoc_codesign
- Apple ld64 LinkEdit.hpp: CS_LINKER_SIGNED
- Apple: Debugging Tool Entitlement, com.apple.security.cs.debugger
- Apple: Hardened Runtime
- Apple: Resolving common notarization issues, the get-task-allow entitlement
- Apple: Disabling and Enabling System Integrity Protection
- Apple Developer Forums: debugserver may not get the taskport of a hardened process
- Apple DevToolsSecurity(8)
- Apple core(5)
- Apple Technical Note TN2124: Mac OS X Debugging Magic, core dumps
- lldb
process save-corehelp text - core(5), Linux manual page
- coredumpctl(1)
- Apple dtrace(1)
- Apple dtruss(1m)
- strace releases
- ltrace
- bpftrace CHANGELOG
- rr project
- rr 5.9.0 release
- rr wiki: Building And Installing, hardware and VM requirements
- Clang UndefinedBehaviorSanitizer
- ISO/IEC 9899:2024 working draft N3220, clause 3.5 undefined behavior
- DWARF Debugging Information Format Version 5
- Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64)
- System V Application Binary Interface, AMD64 Architecture Processor Supplement
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.