xref: /xnu-11417.140.69/tools/lldbmacros/tests/integration_smoke/test_lldb_macros.py (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
1"""Test all LLDB macros using LLDB session.
2
3Usages:
4    1. pytest - `xcrun --sdk macosx.internal pytest --disable-warnings -v tools/lldbmacros/tests/integration_smoke/test_lldb_macros [--remote-gdb 127.0.0.1:8000]`.
5    2. module - `xcrun --sdk macosx.internal python tools/lldbmacros/tests/integration_smoke/test_lldb_macros [127.0.0.1:8000]`.
6    3. macros within an existing LLDB session -
7        * `xcrun --sdk macosx.internal lldb [-c coredump file]` (coredumps are supported as well, see lldb command for details.
8        * `command script import command script import tools/lldbmacros/tests/integration_smoke/test_lldb_macros.py`
9        * If you need a `gdb-remote`, do `gdb [ip=127.0.0.1:]<port>` (the port is usually 8000).
10        * `macro_exec [macro1] [macro2] [...]`.
11
12TODO: extend the integration tests to actually validate correctness.
13"""
14import contextlib
15import functools
16import os.path
17import re
18import signal
19import sys
20import threading
21import typing
22
23import pytest
24import lldb
25
26from lldb_session import AtDeskLLDBGdbSession
27
28
29def _get_task(session: AtDeskLLDBGdbSession, name: str) -> str:
30    return session.exec(f"showtask -F {name}").split('\n')[2].split()[0]
31
32
33_get_init_task = functools.partial(_get_task, 'init')
34
35
36def _arbitrary_task(session: AtDeskLLDBGdbSession) -> str:
37    return session.exec("showalltasks").split('\n')[2].split()[0]
38
39
40def _arbitrary_proc(session: AtDeskLLDBGdbSession, pid: int = 0) -> str:
41    return session.exec(f"showpid {pid}").split('\n')[1].split()[5]
42
43
44def _arbitrary_thread(session: AtDeskLLDBGdbSession, task_name: str = 'init') -> str:
45    return session.exec(f"showtaskthreads -F {task_name}").split('\n')[3].strip().split()[0]
46
47
48def _arbitrary_kext(session: AtDeskLLDBGdbSession, kext_name: str = 'com.apple.BootCache') -> str:
49    return re.search(r"(0XFFFFF\w+)", session.exec(f"showkextmacho {kext_name}")).group(1)
50
51
52def _arbitrary_vm_line(session: AtDeskLLDBGdbSession, row: int, col: int) -> str:
53    return session.exec("showallvm").split('\n')[row].strip().split()[col]
54
55
56def _arbitrary_vm_map(session: AtDeskLLDBGdbSession, index: int = 1) -> str:
57    return _arbitrary_vm_line(session, row=index, col=1)
58
59
60def _arbitrary_vm_node(session: AtDeskLLDBGdbSession) -> str:
61    return session.exec("showallvnodes").stdout.readlines(4)[1].strip().split()[0]
62
63
64def __arbitrary_ipc_line(session: AtDeskLLDBGdbSession, index: int) -> str:
65    return session.exec('showallipc').split('\n')[1].strip().split()[index]
66
67
68def _arbitrary_ipc(session: AtDeskLLDBGdbSession) -> str:
69    return __arbitrary_ipc_line(session, index=2)
70
71
72def _arbitrary_mbuf(session: AtDeskLLDBGdbSession) -> str:
73    return session.exec('mbuf_showactive').split('\n')[3].strip().split()[0]
74
75
76def _arbitrary_proc_channel(session: AtDeskLLDBGdbSession, task_name: str = 'apsd') -> str:
77    proc_id = session.exec(f'showtask -F {task_name}').split('\n')[1].split()[6]
78    return session.exec(f'showprocchannels {proc_id}').split('\n')[1].split()[0]
79
80
81MACROS = [
82    "kgmhelp",
83    ("showraw", "showversion"),  # TODO: find a better one.
84    ("xnudebug", "reload memory"),
85    "showversion",
86    "paniclog",
87    "extpaniclog",
88    "showbootargs",
89    "showlldbtypesummaries",
90    ("walkqueue_head", '<struct queue_entry *> "thread *" "task_threads"'),  # ?
91    ("walklist_entry", '<struct proc *> "struct proc *" "p_sibling"'),  # ?
92    "iotrace",
93    "ttrace",
94    "showsysctls",
95    "showexperiments",
96    "allproc",
97    "zombproc",
98    "zombtasks",
99    "zombstacks",
100    ("showcoalitioninfo", lambda session: session.exec("showallcoalitions").split('\n')[0].split()[0]),
101    "showallcoalitions",
102    "showcurrentforegroundapps",
103    "showallthreadgroups",
104    ("showtaskcoalitions", "-F init"),
105    ("showtask", "-F init"),
106    ("showpid", "0"),
107    ("showproc", _arbitrary_proc),
108    ("showprocinfo",_arbitrary_proc),
109    ("showprocfiles", _arbitrary_proc),
110    ("showtty", lambda session: session.exec("showallttydevs").split('\n')[2].split()[0]),
111    "showallttydevs",
112    "dumpthread_terminate_queue",
113    ("dumpcrashed_thread_queue", None, [pytest.mark.xfail(reason='fails on live-VM coredump - rdar://136215390')]),
114    ("dumpcallqueue", ""),  # TODO: ?
115    "showalltasklogicalwrites",
116    "showalltasks",
117    ("taskforpmap", ""),  # TODO: ?
118    "showterminatedtasks",
119    ("showtaskstacks", "-F init"),  # TODO: does not
120    ("showprocrefs", _arbitrary_task),
121    "showallthreads",
122    "showterminatedthreads",
123    ("showtaskthreads", "-F init"),
124    ("showact", _arbitrary_thread),
125    ("showactstack", _arbitrary_thread),
126    ("switchtoact", _arbitrary_thread),
127    ("switchtoregs", _arbitrary_thread),
128    ("showcallchains", "init"),  # TODO: should work without `load-script-from-symbol-file true`.
129    "showallstacks",
130    "showcurrentstacks",
131    "showcurrentthreads",
132    ("fullbt", "0"),
133    ("fullbtall", None, [pytest.mark.xfail(reason='rdar://136033352')]),
134    "symbolicate",
135    "showinitchild",
136    "showproctree",
137    ("showthreadfortid", _arbitrary_thread),
138    ("showtaskledgers", "-F init"),
139    "showalltaskledgers",
140    "showprocuuidpolicytable",
141    "showalltaskpolicy",
142    "showallsuspendedtasks",
143    "showallpte",
144    "showallrefcounts",
145    "showallrunnablethreads",
146    "showallschedusage",
147    "showprocfilessummary",
148    ("workinguserstacks", _get_init_task),
149    ("workingkuserlibraries", _get_init_task),
150    ("showstackaftertask", "-F init"),
151    ("showstackafterthread", _get_init_task),
152    ("showkextmacho", "com.apple.BootCache"),
153    ("showkmodaddr", _arbitrary_kext),
154    "showallkmods",
155    "showallknownkmods",
156    ("addkext", "-N com.apple.BootCache"),
157    ("addkextaddr", _arbitrary_kext),
158    ("showzpcpu", ""),  # TODO: ?
159    "memstats",
160    "showpgz",
161    ("whatis", _arbitrary_kext),
162    "showzcache",
163    "zprint",
164    "showkalloctypes",
165    "showzchunks",
166    "showallzchunks",
167    "showbtref",
168    "_showbtlibrary",
169    "showbtlog",
170    "showbtlogrecords",
171    "zstack_showzonesbeinglogged",
172    "zstack",
173    "zstack_inorder",
174    "zstack_findleak",
175    "zstack_findelem",
176    "zstack_findtop",
177    ("showpcpu", ""),  # TODO: ?
178    "showioalloc",
179    "showselectmem",
180    ("showtaskvme", _arbitrary_task),
181    "showallvm",
182    ("showtaskvm", functools.partial(_arbitrary_vm_line, row=1, col=0)),
183    "showallvmstats",
184    ("showmap", _arbitrary_vm_map),
185    ("showmapvme", _arbitrary_vm_map),
186    ("showrangevme", "-N 1"),
187    "showvmtagbtlog",
188    ("showmapranges", _arbitrary_vm_map, [pytest.mark.xfail(reason='rdar://136137832')]),
189    ("showmapwired", _arbitrary_vm_map),
190    "showallmounts",
191    "showvnodepath",
192    ("showvnodedev", ""),  # TODO: session.exec("showallvnodes").stdout.readlines(4)[1].strip().split()[0], you need to cancel it in the middle.
193    ("showvnodelocks", ""),  # TODO: session.exec("showallvnodes").stdout.readlines(4)[1].strip().split()[0], you need to cancel it in the middle.
194    ("showproclocks", ""),  # TODO: session.exec("showallvnodes").stdout.readlines(4)[1].strip().split()[0], you need to cancel it in the middle.
195    "showvnode",
196    "showvolvnodes",
197    "showvolbusyvnodes",
198    "print_vnode",
199    "showworkqvnodes",
200    "shownewvnodes",
201    "showprocvnodes",
202    "showallprocvnodes",
203    ("showlock", ""),  # TODO: ?
204    ("showthreadrwlck", ""),  # TODO: thread?
205    "showallrwlckheld",
206    ("tryfindrwlckholders", _arbitrary_thread),
207    ("getthreadfromctid", ""),  # TODO: ctid?
208    ("getturnstilefromctsid", ""),  # TODO: ctid?
209    ("showkernapfsreflock", ""),  # TODO: kern_apfs_reflock_t?
210    "showbootermemorymap",
211    "show_all_purgeable_objects",
212    "show_all_purgeable_nonvolatile_objects",
213    "show_all_purgeable_volatile_objects",
214    ("showmapcopyvme", _arbitrary_vm_map, [pytest.mark.xfail(reason='rdar://136137832')]),
215    ("showmaptpro", ""),  # TODO: vm_map?
216    ("showvmpage", ""),  # TODO: vm_page?
217    ("showvmobject", "kernel_object_default"),
218    "showallvmobjects",
219    "showvmtags",
220    ("showtaskloadinfo", _arbitrary_task),
221    ("vmpagelookup", "kernel_object_default 0"),
222    ("vmpage_get_phys_page", "<vm_page_t>"),
223    ("vmpage_from_phys_page", "<ppnum_t>"),
224    "vmpage_unpack_ptr",
225    ("calcvmpagehash", "kernel_object_default 0"),
226    ("showallocatedzoneelement", "<address of zone>"),  # TODO: ?
227    ("scan_vm_pages", '-A -N 1'),
228    ("vmobjectwalkpages", "kernel_object_default"),
229    "show_all_apple_protect_pagers",
230    ("show_apple_protect_pager", lambda session: session.exec('show_all_apple_protect_pagers').split('\n')[1].strip().split()[0]),  # no protected pager :\
231    "show_all_shared_region_pagers",
232    ("show_shared_region_pager", lambda session: session.exec('show_all_shared_region_pagers').split('\n')[2].strip().split()[1]),
233    ("show_all_dyld_pagers", None, [pytest.mark.xfail(reason='rdar://139146013')]),
234    ("show_dyld_pager", lambda session: session.exec('show_all_dyld_pagers').split('\n')[2].strip().split()[1]),
235    "show_console_ring",
236    "showjetsamsnapshot",
237    ("showjetsamband", "0"),
238    "showvnodecleanblk",
239    "showvnodedirtyblk",
240    ("vm_page_lookup_in_map", "<map> <vaddr>"),  # TODO: ?
241    ("vm_page_lookup_in_object", "<object> <offset>"),  # TODO: ?
242    ("vm_page_lookup_in_compressor_pager", "<pager> <offset>"),  # TODO: ?
243    ("vm_page_lookup_in_compressor", "<slot>"),  # TODO: ?
244
245    # vm_pageout.py
246    "showvmpageoutqueues",
247    "showvmpageoutstats",
248    "showvmpageouthistory",
249
250    # taskinfo.py
251    'showmemorystatus',
252    ('showtasksuspendsources', _arbitrary_task),
253    ('showtasksuspendstats', _arbitrary_task),
254
255    # TODO: understand why it works only on live VMs, in the meantime moved to TOO_LONG.
256    # "show_all_vm_named_entries",
257    # ("show_vm_named_entry", lambda session: session.exec('show_all_vm_named_entries').split('\n')[4].strip().split()[0]),
258
259    ("showmaprb", "<vm_map>"),  # TODO: ?
260    "show_all_owned_objects",
261    ("show_task_owned_objects", lambda session: session.exec('show_all_owned_objects').split('\n')[3].split()[0]),
262    "showdeviceinfo",
263    "showdiagmemthresholds",
264    ("showbankaccountstopay", lambda session: session.exec('showallbanktasklist').split('\n')[1].strip().split()[0]),
265    ("showbankaccountstocharge", lambda session: session.exec('showallbanktasklist').split('\n')[1].strip().split()[0]),
266    "showallbanktasklist",
267    "showallbankaccountlist",
268    "showwaitq",
269    "showglobalwaitqs",
270    "showglobalqstats",
271    ("sendcore", "127.0.0.1"),  # TODO: reconsider using generic kdumpd server.
272    ("sendsyslog", "127.0.0.1"),  # TODO: reconsider using generic kdumpd server.
273    ("sendpaniclog", "127.0.0.1"),  # TODO: reconsider using generic kdumpd server.
274    "disablecore",
275    "resume_on",
276    "resume_off",
277    "getdumpinfo",
278    ("kdp-reenter", "0"),
279    "kdp-reboot",
280    ("setdumpinfo", '"" "" "" 0'),  # do not change anything.
281    "kdpmode",
282    "showallclasses",
283    ("showobject", "<OSOObject *>"),  # TODO: ?
284    ("dumpobject", "<OSOObject *>"),  # TODO: same as `showobject`.
285    ("setregistryplane", "0"),
286    ("showregistryentry", lambda session: re.search(r'<object (0x[0-9a-f]+),', session.exec('showregistry').split('\n')[3].strip()).group(1)),
287    "showregistry",
288    ("findregistryentry", "VMAC400AP"),  # TODO: reconsider, might change between VM and non-VM.
289    ("findregistryentries", "AppleHWAccess"),
290    ("findregistryprop", lambda session: f"{re.search(r'<object (0x[0-9a-f]+),', session.exec('findregistryentries AppleHWAccess')).group(1)} IOSleepSupported"),
291    ("readioport8", "0"),
292    ("readioport16", "0"),
293    ("readioport32", "0"),
294    ("writeioport8", "0 0"),
295    ("writeioport16", "0 0"),
296    ("writeioport32", "0 0"),
297    ("showioservicepm", "<IOServicePM *>"),  # TODO: ?
298    ("showiopmqueues", None, [pytest.mark.xfail(reason='rdar://136151068')]),
299    ("showiopminterest", "<IOService *>"),  # TODO: ?
300    "showinterruptvectors",
301    ("showiokitclasshierarchy", "<class?>"),  # TODO: ?
302    "showinterruptcounts",
303    "showinterruptstats",
304    "showpreoslog",
305    ("showeventsources", "<IOWorkLoop *>"),  # TODO: ?
306    "showcarveouts",
307    ("showipc", _arbitrary_ipc),
308    ("showtaskipc", _arbitrary_task),
309    "showallipc",
310    "showipcsummary",
311    ("showrights", _arbitrary_ipc),
312    ("showtaskrights", functools.partial(__arbitrary_ipc_line, index=0)),
313    ("countallvouchers", None, [pytest.mark.xfail(reason='rdar://136138236')]),
314    ("showtaskrightsbt", functools.partial(__arbitrary_ipc_line, index=0)),
315    ("findportrights", "<ipc_port_t *>"),  # TODO: ?
316    "showpipestats",
317    ("showtaskbusyports", _arbitrary_task),
318    ("findkobjectport", "<kobject-addr>"),  # TODO: ?
319    ("showtaskbusypsets", _arbitrary_task),
320    "showallbusypsets",
321    "showallpsets",
322    ("showbusyportsummary", None, [pytest.mark.xfail(reason='rdar://136138456')]),
323    ("showport", lambda session: session.exec('showallports').split('\n')[1].split()[0]),
324    ("showpset", lambda session: session.exec('showallpsets').split('\n')[1].split()[0]),
325    ("showkmsg", "<ipc_kmsg_t"),  # TODO: ?
326    "showalliits",
327    ("showallimportancetasks", None, [pytest.mark.xfail(reason='fails on MTE enabled machines -> rdar://136151386')]),
328    ("showipcimportance", lambda session: session.exec('showallimportancetasks').split('\n')[1].strip().split()[0]),
329    ("showivac", lambda session: session.exec('showglobalvouchertable').split('\n')[1].strip().split()[2]),
330    "showglobalvouchertable",
331    ("showivacfreelist", lambda session: session.exec('showglobalvouchertable').split('\n')[1].strip().split()[2]),
332    "showallvouchers",
333    ("showvoucher", lambda session: session.exec('showallvouchers').split('\n')[1].strip().split()[0]),
334    ("showtasksuspenders", _arbitrary_task),  # TODO: find a way to get a suspend task.
335    ("showmqueue", "<struct ipc_mqueue *>"),  # TODO: ?
336    ("readphys", "1 1337"),
337    ("writephys", "1 1337 0"),
338    ("pmap_walk", "<pmap_t> <virtual offset>"),  # TODO: ?
339    ("ttep_walk", "<root_ttep> <virtual offset>"),  # TODO: ?
340    ("decode_tte", "1 1"),
341    ("pv_walk", ""),  # TODO?
342    ("kvtophys", "<kernel virtual address>"),  # TODO: ?
343    ("phystokv", "0"),  # TODO: ?
344    ("phystofte", "<physical address>"),  # TODO: ?
345    ("showpte", "<pte_va>"),  # TODO: ?
346    ("pv_check", "<pte>/<physical address>"),  # TODO: ?
347    ("pmapsforledger", "0"),
348    ("pmappaindex", "<pai>/<physical address>"),  # TODO: ?
349    "mbuf_stat",
350    "mbuf_decode",
351    ("mbuf_dumpdata", _arbitrary_mbuf),
352    ("mbuf_walkpkt", _arbitrary_mbuf),
353    ("mbuf_walk", _arbitrary_mbuf),
354    ("mbuf_buf2slab", _arbitrary_mbuf),
355    ("mbuf_buf2mca", _arbitrary_mbuf),
356    ("mbuf_slabs", lambda session: session.exec('mbuf_slabstbl').split('\n')[3].strip().split()[0]),
357    "mbuf_slabstbl",
358    "mbuf_walk_slabs",
359    ("mbuf_show_m_flags", _arbitrary_mbuf),
360    ("mbuf_showpktcrumbs", _arbitrary_mbuf),
361    "mbuf_showactive",
362    "mbuf_showinactive",
363    "mbuf_show_type_summary",
364    "mbuf_showmca",
365    "mbuf_showall",
366    ("mbuf_countchain", _arbitrary_mbuf),
367    "mbuf_topleak",
368    "mbuf_largefailures",
369    ("mbuf_traceleak", "<mtrace *>"),  # TODO: ?
370    ("mcache_walkobj", "<mcache_obj_t *>"),  # TODO: ?
371    "mcache_stat",
372    "mcache_showcache",
373    "mbuf_wdlog",
374    ("net_get_always_on_pktap", None, [pytest.mark.xfail(reason='fails on live vm coredump - rdar://136270822')]),
375    "ifconfig_dlil",
376    "showifaddrs",
377    "ifconfig",
378    "showifnets",
379    "showdetachingifnets",
380    "showorderedifnets",
381    "showifmultiaddrs",
382    "showinmultiaddrs",
383    "showin6multiaddrs",
384    "showsocket",
385    "showprocsockets",
386    "showallprocsockets",
387    "show_rt_inet",
388    "show_rt_inet6",
389    "rtentry_showdbg",
390    "inm_showdbg",
391    "ifma_showdbg",
392    "ifpref_showdbg",
393    "ndpr_showdbg",
394    "nddr_showdbg",
395    "imo_showdbg",
396    "im6o_showdbg",
397    "rtentry_trash",
398    ("show_rtentry", "<rtentry *>"),  # TODO: ?
399    "inm_trash",
400    "in6m_trash",
401    "ifma_trash",
402    "show_socket_sb_mbuf_usage",
403    ("mbuf_list_usage_summary", "<struct mbuf *>"),  # TODO: ?
404    "show_kern_event_pcbinfo",
405    "show_kern_control_pcbinfo",
406    "show_unix_domain_pcbinfo",
407    "show_tcp_pcbinfo",
408    "show_udp_pcbinfo",
409    "show_rip_pcbinfo",
410    "show_mptcp_pcbinfo",
411    "show_domains",
412    ("tcp_count_rxt_segments", "<tcpcb *>"),  # TODO: ?
413    ("tcp_walk_rxt_segments", "<tcpcb *>"),  # TODO: ?
414    ("showprocchannels", functools.partial(__arbitrary_ipc_line, index=5)),
415    ("showchannelrings", _arbitrary_proc_channel),
416    "showskmemcache",
417    "showskmemslab",
418    "showskmemarena",
419    "showskmemregions",
420    "showskmemregion",
421    "showchannelupphash",
422    "shownetns",
423    "showallnetnstokens",
424    "shownetnstokens",
425    "shownexuschannels",
426    ("showprocnecp", "<proc_t>"),  # TODO: ?
427    "shownexuses",
428    "showflowswitches",
429    ("showcuckoohashtable", "<struct cuckoo_hashtable *>"),  # TODO: ?
430    "showprotons",
431    ("showthreaduserstack", "<thread_ptr>"),  # TODO: ?
432    ("printuserdata", " <task_t> 0 b"),  # TODO: ?
433    ("showtaskuserargs", "<task_t>"),  # TODO: ?
434    ("showtaskuserstacks", "-F init", [pytest.mark.xfail(reason='fails on MTE enabled machines - rdar://136151909')]),
435    ("showtaskuserlibraries", "<task_t>"),  # TODO: ?
436    ("showtaskuserdyldinfo", "<task_t>"),  # TODO: ?
437    ("savekcdata", " <kcdata_descriptor_t>"),  # TODO: ?
438    ("pci_cfg_read", "<bits=8,16,32> <bus> <device> <function> <offset>"),  # TODO: ??
439    ("pci_cfg_write", "<bits=8,16,32> <bus> <device> <function> <offset> <value>"),  # TODO: ??
440    ("pci_cfg_dump", "0 0 0", [pytest.mark.xfail(reason='fails on MTE enabled machines - rdar://136198241')]),
441    "pci_cfg_scan",
442    "showallprocrunqcount",
443    "showinterrupts",
444    ("showactiveinterrupts", "<AppleInterruptController *>"),  # TODO: ?
445    "showirqbyipitimerratio",
446    "showinterruptsourceinfo",
447    "showcurrentabstime",
448    ("showschedclutch", "<processor_set_t>"),  # TODO: ?
449    ("showschedclutchroot", "<struct sched_clutch_root *>"),  # TODO: ?
450    ("showschedclutchrootbucket", "<struct sched_clutch_root *>"),  # TODO: ?
451    ("showschedclutchbucket", "<struct sched_clutch_bucket *>"),  # TODO: ?
452    ("abs2nano", "1337"),
453    "showschedhistory",
454    ("showrunq", "<struct run_queue *>"),  # TODO:
455    "showscheduler",
456    "showallprocessors",
457    ("showwqthread", lambda session: session.exec(f'showprocworkqueue {_arbitrary_proc(session, pid=1)}').strip().split('\n')[-1].strip().split()[0],
458     pytest.mark.xfail(reason='rdar://136138760')),
459    ("showprocworkqueue", functools.partial(_arbitrary_proc, pid=1)),
460    "showallworkqueues",
461    "showknote",
462    "showkqfile",
463    "showkqworkq",
464    "showkqworkloop",
465    "showkqueue",
466    "showprocworkqkqueue",
467    "showprockqueues",
468    "showprocknotes",
469    "showallkqueues",
470    "showkqueuecounts",
471    ("showcalloutgroup", "threads", [pytest.mark.xfail(reason='fails on MTE enabled machines - rdar://136198396')]),
472    # ("showcalloutgroup", "<struct thread_call_group *>"),  # TODO: session.exec('showallcallouts').stdout.readline().strip().split()[-1][1:-1]
473    ("showallcallouts", None, [pytest.mark.xfail(reason='rdar://136033401')]),
474    ("recount", "task -F init"),  # TODO: consider `thread`, `coalition` and `processor`.
475    "showmcastate",
476    "longtermtimers",
477    "processortimers",
478    "showcpudata",
479    "showtimerwakeupstats",
480    "showrunningtimers",
481    ("readmsr64", "0"),
482    ("writemsr64", "0 0"),  # does not work without kdp.
483    ("q_iterate", "<struct queue_entry *> '<element type>' <field name>"),  # TODO: ?
484    "lbrbt",
485    ("lapic_read32", "0"),  # TODO: validate on Intel 64-bit architecture.
486    ("lapic_write32", "0 0"),  # TODO: validate on Intel 64-bit architecture.
487    "lapic_dump",
488    ("ioapic_read32", "0"),  # TODO: validate on Intel 64-bit architecture.
489    ("ioapic_write32", "0 0"),  # TODO: validate on Intel 64-bit architecture.
490    "ioapic_dump",
491    ("showstructpacking", "showstructpacking pollfd"),
492    ("showallipcimportance", None, [pytest.mark.xfail(reason='fails on MTE enabled machines -> rdar://136151386')]),
493    ("showturnstile", lambda session: session.exec('showallturnstiles').split('\n')[1].strip().split()[0]),
494    "showturnstilehashtable",
495    "showallturnstiles",
496    "showallbusyturnstiles",
497    "showthreadbaseturnstiles",
498    "showthreadschedturnstiles",
499    "kasan",
500    "showkdebugtypefilter",
501    "showkdebug",
502    "showktrace",
503    "showkdebugtrace",
504    ("savekdebugtrace", "/tmp/dtrace"),
505    ("xi", ""),  # TODO: ??
506    ("newbt", ""),  # TODO: ??
507    "parseLR",
508    ("parseLRfromfile", ""),  # TODO: ?
509    "showallulocks",
510    "showallntstat",
511    "zonetriage",
512    "zonetriage_freedelement",
513    "zonetriage_memoryleak",
514    ("decode_sysreg", "esr_el1 0x96000021"),
515    ("showcounter", "<scalable_counter_t>"),  # TODO: ?
516    ("showosrefgrp", lambda session: session.exec('showglobaltaskrefgrps').split('\n')[1].strip().split()[0]),
517    ("showosrefgrphierarchy",lambda session: session.exec('showglobaltaskrefgrps').split('\n')[1].strip().split()[0]),
518    "showglobaltaskrefgrps",
519    ("showtaskrefgrps", "<task *>"),  # TODO: ?
520    "showallworkloadconfig",   # always empty :\
521    ("showworkloadconfig", lambda session: session.exec('showallworkloadconfig').split('\n')[1].strip().split()[0]),
522    ("showworkloadconfigphases", lambda session: session.exec('showallworkloadconfig').split('\n')[1].strip().split()[0]),
523    "showlogstream",
524    "showlq",
525    ("showmsgbuf", "<struct msgbuf *>"),  # TODO: ?
526    "systemlog",
527    "shownvram",
528    "showallconclaves",
529    "showexclavesresourcetable",
530    ("showesynctable", "<ht_t/ht_t *>"),  # TODO: ?
531]
532
533TOO_LONG = [
534    "showallcsblobs",
535    "triagecsblobmemory",
536    "showallvnodes",
537    "showallbusyvnodes",
538    "pci_cfg_dump_all",
539    "check_pmaps",
540    "showallmappings",  # may take up to 30 minutes!
541    "showptusage",
542    "show_all_vm_named_entries",
543    "show_vm_named_entry",
544    "showallvme",
545    "vm_scan_all_pages",
546    "showallrights",
547    "showallbusyports",  # rdar://136138456
548    "showallports",
549    "countallports",  # rdar://136138236
550    "showregistryprops",
551    "showportsendrights",  # rdar://136138614
552    "showthreadswaitingforuserserver",  # Stuck forever on some devices.
553]
554
555INTERACTIVE = [
556    "beginusertaskdebugging"
557]
558
559OBSOLETE = [
560    "showkerneldebugbuffercpu",
561    "showkerneldebugbuffer",
562    "dumprawtracefile"
563]
564IGNORES = TOO_LONG + INTERACTIVE + OBSOLETE
565
566
567
568@pytest.fixture(scope='session')
569def lldb_gdb_session(pytestconfig: pytest.Config) -> AtDeskLLDBGdbSession:
570    if pytestconfig.getoption('--use-existing-debugger'):
571        session = AtDeskLLDBGdbSession(lldb.debugger.GetCommandInterpreter())
572        session.refresh()
573        yield session
574        return
575
576    with AtDeskLLDBGdbSession.create(pytestconfig.getoption('--gdb-remote')) as session:
577        yield session
578
579
580@pytest.fixture(scope='session')
581def ignores(pytestconfig: pytest.Config) -> set[str]:
582    return set(IGNORES) | set(pytestconfig.getoption('--extra-ignores').split(','))
583
584
585@pytest.fixture
586def _skip_if_dirty(request: pytest.FixtureRequest) -> None:
587    if request.session.stash.get('dirty', False):
588        pytest.skip('LLDB session is "dirty", skipping.')
589
590
591@pytest.fixture
592def _timed_action(_skip_if_dirty: None, request: pytest.FixtureRequest) -> typing.Callable[[float], typing.ContextManager[None]]:
593    def kill_session():
594        request.session.stash['dirty'] = True
595        os.kill(os.getpid(), signal.SIGTERM)
596
597    @contextlib.contextmanager
598    def timed_context(timeout: float) -> None:
599        timed_out = False
600
601        def fail(*_):
602            nonlocal timed_out
603            timed_out = True
604
605        signal.signal(signalnum=signal.SIGTERM, handler=fail)
606
607        timer = threading.Timer(interval=timeout, function=kill_session)
608        timer.start()
609        try:
610            yield
611        finally:
612            timer.cancel()
613            signal.signal(signal.SIGTERM, signal.SIG_DFL)  # unmask
614
615            if timed_out:
616                pytest.fail('The LLDB session is stuck, self-destructing.')
617
618    return timed_context
619
620
621MACROS = [(i, None, []) if isinstance(i, str) else (i if len(i) == 3 else (*i, [])) for i in MACROS]
622
623
624@pytest.mark.parametrize('macro', [pytest.param(i[:-1], marks=i[-1], id=i[0]) for i in MACROS])
625def test_macro_exec(_timed_action: typing.Callable[[float], typing.ContextManager[None]],
626                    macro: tuple[str, typing.Optional[str], typing.Optional[list[pytest.Mark]]],
627                    lldb_gdb_session: AtDeskLLDBGdbSession) -> None:
628    macro, args_or_func = macro
629    if hasattr(args_or_func, '__call__'):
630        try:
631            macro = f'{macro} {args_or_func(lldb_gdb_session)}'
632        except Exception:
633            pytest.skip('Unable to evaluate additional argument(s).')
634    elif args_or_func is not None:
635        if args_or_func == '' or '<' in args_or_func:
636            pytest.skip('The test is not implemented, skipping.')
637        macro = f'{macro} {args_or_func}'
638
639    with _timed_action(timeout=120.):
640        lldb_gdb_session.exec(macro)
641
642
643def test_macro_coverage(lldb_gdb_session: AtDeskLLDBGdbSession, ignores: set[str]) -> None:
644    stdout = lldb_gdb_session.exec('help')
645    _start_after = 'Current user-defined commands:'
646    _end_before = "For more information on any command, type 'help <command-name>'."
647    print(f'{stdout=}')
648    user_defined_macros = stdout[stdout.find(_start_after) + len(_start_after): stdout.find(_end_before)]
649    print(f'{user_defined_macros}')
650    macro_extractor = re.compile(r'(\w+-\w+|\w+)\s+--\s')
651
652    covered = {i[0].split()[0]  # All subcommands are aggregated under a single command (see `help` command).
653               for i in MACROS} | ignores
654    macros = set()
655    for line in user_defined_macros.split('\n'):
656        if len(line.strip()) == 0:
657            continue
658        match = macro_extractor.search(line)
659        if match is None:
660            continue
661        macros.add(match.group(1))
662    print('macros', macros)
663    print('covered', covered)
664    # macros = {i for i in macros if not i.startswith('_')}
665    assert macros == covered, f'not_covered_by_tests=`{macros - covered}`, missing_macros=`{covered - macros}`'
666    # assert not_covered == []
667
668
669def macro_exec(debugger, command, result, internal_dict) -> None:
670    """Run tests for all LLDB macros (except for IGNORES, see above).
671
672    Usage:
673        (lldb) test_macro_exec
674        -> Test all macros
675
676        (lldb) test_macro_exec [macro_1] [macro 2] ...
677        -> Test specific macro(s).
678    """
679    no_color = '--no-color' in command
680    if no_color:
681        command = command.replace('--no-color', '').strip()
682
683    abs_file = os.path.abspath(__file__)
684    abs_root = os.path.dirname(abs_file)
685    os.chdir(abs_root)
686
687    result.ret = pytest.main(
688        (['--color', 'no'] if no_color else []) +
689        ['--disable-warnings', '--rootdir', abs_root,
690         f'--durations={len(MACROS)}',  # It is the maximal number of test cases.
691         '-s' if len(command.strip()) > 0 else '-vv',
692         '-k', macro_exec.__name__, abs_file, '--use-existing-debugger',
693        ] + [f'{abs_file}::{test_macro_exec.__name__}[{i}]'
694             # Supporting multi-word commands, e.g.: sub-command, options, anything between "" / '' / a single word.
695             for i in re.split('\'(.+?)\'|"(.+?)"|(\\S+)\\s+|(\\S+)$', command)
696             if i and i.strip()])
697
698
699def macro_coverage(debugger, command, result, internal_dict) -> None:
700    """Run the macro coverage test.
701
702    Usage:
703        (lldb) test_macro_coverage
704        -> Test macro coverage (see also `IGNORES` or --extra-ignores).
705    """
706    abs_file = os.path.abspath(__file__)
707    abs_root = os.path.dirname(abs_file)
708    os.chdir(abs_root)
709
710    result.ret = pytest.main(
711        (['--color', 'no'] if '--no-color' in command else []) + [
712         '--disable-warnings', '--rootdir', abs_root,
713         '-v', '-k', test_macro_coverage.__name__, abs_file,
714         '--use-existing-debugger', '--extra-ignores', f'hwtrace,{macro_exec.__name__},{macro_coverage.__name__}'
715    ])
716
717
718def __lldb_init_module(debugger, internal_dict):
719    debugger.HandleCommand(f'command script add -f {macro_exec.__module__}.{macro_exec.__name__} {macro_exec.__name__}')
720    print(f'The `{macro_exec.__name__}` command has been installed and is ready to use.')
721
722    debugger.HandleCommand(f'command script add -f {macro_coverage.__module__}.{macro_coverage.__name__} {macro_coverage.__name__}')
723    print(f'The `{macro_coverage.__name__}` command has been installed and is ready to use.')
724
725
726if __name__ == '__main__':
727    remote_gdb = sys.argv[1]
728    pytest.main(['--disable-warnings', '-v', __file__, '--gdb-remote', remote_gdb])
729