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