xref: /xnu-10002.61.3/doc/debugging.md (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
1*0f4c859eSApple OSS Distributions# XNU debugging
2*0f4c859eSApple OSS Distributions
3*0f4c859eSApple OSS Distributionsxnu’s debugging macros are compatible with both Python 2 and 3. In practice, this means that Python 3
4*0f4c859eSApple OSS Distributionsfeatures are unavailable and some Python 2 syntax is not allowed. Unfortunately, any syntax error will
5*0f4c859eSApple OSS Distributionsprevent use of all the macros, as they’re all imported into the same scripting environment.
6*0f4c859eSApple OSS Distributions
7*0f4c859eSApple OSS Distributions## Compatibility
8*0f4c859eSApple OSS Distributions
9*0f4c859eSApple OSS DistributionsAvoid introducing specific compatibility shims, as there are a few existing ones that come with
10*0f4c859eSApple OSS DistributionsPython 2 and 3:
11*0f4c859eSApple OSS Distributions
12*0f4c859eSApple OSS Distributions* **six** has helpers that work in both Python 2 and 3, for things like the string type change
13*0f4c859eSApple OSS Distributions* **future** backports features from Python 3 to Python 2
14*0f4c859eSApple OSS Distributions
15*0f4c859eSApple OSS DistributionsFor example, Python 2 contains **range** and **xrange**. Python 3 contains only **range** which has
16*0f4c859eSApple OSS Distributions**xrange** semantics. The simplest solution is to port your code and use Python 3 way:
17*0f4c859eSApple OSS Distributions
18*0f4c859eSApple OSS Distributions```
19*0f4c859eSApple OSS Distributions# Use backported range from Python 3
20*0f4c859eSApple OSS Distributionsfrom builtins import range
21*0f4c859eSApple OSS Distributions
22*0f4c859eSApple OSS Distributions# Use range on both Python 2/3 runtimes
23*0f4c859eSApple OSS Distributionsfor x in range(....):
24*0f4c859eSApple OSS Distributions   ....
25*0f4c859eSApple OSS Distributions```
26*0f4c859eSApple OSS Distributions
27*0f4c859eSApple OSS DistributionsBe very careful about using imports from 'future' library. Some of them are **very invasive** and change
28*0f4c859eSApple OSS Distributionsbehavior of your code. This may cause strange runtime errors. For example:
29*0f4c859eSApple OSS Distributions
30*0f4c859eSApple OSS Distributions```
31*0f4c859eSApple OSS Distributions# Changes modules handling logic to make your code working with std library reorg (PEP 3108)
32*0f4c859eSApple OSS Distributionsfrom future import standard_library
33*0f4c859eSApple OSS Distributionsstandard_library.install_aliases()
34*0f4c859eSApple OSS Distributions
35*0f4c859eSApple OSS Distributions# Replaces lot of common types like str with future's Python 3 backports.
36*0f4c859eSApple OSS Distributionsfrom builtins import *
37*0f4c859eSApple OSS Distributions```
38*0f4c859eSApple OSS Distributions
39*0f4c859eSApple OSS Distributions## Handling strings
40*0f4c859eSApple OSS Distributions
41*0f4c859eSApple OSS DistributionsMacros use strings produced from the LLDB runtime. They must use **six** when doing certain operations
42*0f4c859eSApple OSS Distributionsto avoid exceptions. Until the transition is done, these canonical ways of dealing with strings cannot
43*0f4c859eSApple OSS Distributionsbe used:
44*0f4c859eSApple OSS Distributions
45*0f4c859eSApple OSS Distributions* Using Unicode literals by default:
46*0f4c859eSApple OSS Distributions   `from __future__ import unicode_literals`
47*0f4c859eSApple OSS Distributions* **f-strings**
48*0f4c859eSApple OSS Distributions
49*0f4c859eSApple OSS DistributionsSome advice:
50*0f4c859eSApple OSS Distributions
51*0f4c859eSApple OSS Distributions* Use byte strings explicitly when dealing with memory and not strings:
52*0f4c859eSApple OSS Distributions  `b'string'`
53*0f4c859eSApple OSS Distributions* Always properly encode/decode raw data to/from strings before passing it around, with `six.ensure_str` or
54*0f4c859eSApple OSS Distributions  `six.ensure_bytes`.
55*0f4c859eSApple OSS Distributions
56*0f4c859eSApple OSS DistributionsImproperly-typed strings will raise *different* exceptions on each runtime.
57*0f4c859eSApple OSS Distributions
58*0f4c859eSApple OSS Distributions* Python 2 raises codec exceptions when printing strings.
59*0f4c859eSApple OSS Distributions* Python 3 complains about concatenation of objects of incompatible types (bytes and strings).
60*0f4c859eSApple OSS Distributions
61*0f4c859eSApple OSS Distributions### No convenient, common string type
62*0f4c859eSApple OSS Distributions
63*0f4c859eSApple OSS DistributionsWhile it is possible to use future’s **newstr** to backport new string type to Python 3, there are
64*0f4c859eSApple OSS Distributionsissues with the Scripting Bridge (SB) API from LLDB. Python 3 will work out of the box but Python 2
65*0f4c859eSApple OSS Distributionswill complain because **newstr** maps to **unicode**. SB exposes **const char \*** as a native string,
66*0f4c859eSApple OSS Distributionsor just **str** in Python 2. For Python 2 we would have to explicitly encode all Unicode strings
67*0f4c859eSApple OSS Distributionsbefore calling the API.
68*0f4c859eSApple OSS Distributions
69*0f4c859eSApple OSS DistributionsAnother problem is that literals in form `'string'` are no longer compatible with unicode and need
70*0f4c859eSApple OSS Distributionsto be switched to `u'string'`. This can be changed with single import at the top of the file, but
71*0f4c859eSApple OSS Distributionsin some scenarios byte strings are expected. That change would require checking all strings in the
72*0f4c859eSApple OSS Distributionscode and changing some back to  `b'string'`.
73*0f4c859eSApple OSS Distributions
74*0f4c859eSApple OSS DistributionsHere’s an example of just how pervasive a change would be because this code would break in Python 2:
75*0f4c859eSApple OSS Distributions
76*0f4c859eSApple OSS Distributions```
77*0f4c859eSApple OSS Distributionsfrom xnu import *
78*0f4c859eSApple OSS Distributions
79*0f4c859eSApple OSS Distributions@lldb_type_summary(['type'])
80*0f4c859eSApple OSS Distributionsdef print_summary():
81*0f4c859eSApple OSS Distributions   ....
82*0f4c859eSApple OSS Distributions```
83*0f4c859eSApple OSS Distributions
84*0f4c859eSApple OSS DistributionsThe result is that we have non-unicode literal being registered with unicode API in Python 3.
85*0f4c859eSApple OSS DistributionsUnfortunately `'type' != b'type'` and thus LLDB will never match the type when printing summaries.
86*0f4c859eSApple OSS Distributions
87*0f4c859eSApple OSS DistributionsUsing native strings and literals allows for only minimal code changes to the macros that are still
88*0f4c859eSApple OSS Distributionscompatible with other projects using Python 2.
89*0f4c859eSApple OSS Distributions
90*0f4c859eSApple OSS Distributions### Check that an object is a string
91*0f4c859eSApple OSS Distributions
92*0f4c859eSApple OSS DistributionsAvoid testing for `str` explicitly like `type(obj) == str`. This won’t work correctly as Python 2
93*0f4c859eSApple OSS Distributionshas multiple string types (`unicode`, `str`). Additionally, compatibility shims might introduce new
94*0f4c859eSApple OSS Distributionsstring types.
95*0f4c859eSApple OSS Distributions
96*0f4c859eSApple OSS DistributionsInstead, always use an inheritance-sensitive like like `isinstance(obj, six.string_types)`.
97*0f4c859eSApple OSS Distributions
98*0f4c859eSApple OSS Distributions### Dealing with binary data
99*0f4c859eSApple OSS Distributions
100*0f4c859eSApple OSS DistributionsPython 2 bytes and strings are the same thing. This was the wrong design decision and Python 3
101*0f4c859eSApple OSS Distributions(wisely) switched to using a separate type for human text. This lack of distinction in Python 2
102*0f4c859eSApple OSS Distributionscaused many programming errors, so it’s recommended to use **bytearray**, **bytes**, and
103*0f4c859eSApple OSS Distributions**memoryviews** instead of a string. If a string is really required, encode the raw data explicitly
104*0f4c859eSApple OSS Distributionsusing an escape method.
105*0f4c859eSApple OSS Distributions
106*0f4c859eSApple OSS Distributions### Accessing large amounts of binary data (or accessing small amounts frequently)
107*0f4c859eSApple OSS Distributions
108*0f4c859eSApple OSS DistributionsIn case you're planning on accessing large contiguous blocks of memory (e.g. reading a whole 10KB of memory),
109*0f4c859eSApple OSS Distributionsor you're accessing small semi-contiguous chunks (e.g. if you're parsing large structured data), then it might
110*0f4c859eSApple OSS Distributionsbe hugely beneficial performance-wise to make use of the `io.SBProcessRawIO` class. Furthermore, if you're in
111*0f4c859eSApple OSS Distributionsa hurry and just want to read one specific chunk once, then it might be easier to use `LazyTarget.GetProcess().ReadMemory()`
112*0f4c859eSApple OSS Distributionsdirectly.
113*0f4c859eSApple OSS Distributions
114*0f4c859eSApple OSS DistributionsIn other words, avoid the following:
115*0f4c859eSApple OSS Distributions
116*0f4c859eSApple OSS Distributions```
117*0f4c859eSApple OSS Distributionsdata_ptr = kern.GetValueFromAddress(start_addr, 'uint8_t *')
118*0f4c859eSApple OSS Distributionswith open(filepath, 'wb') as f:
119*0f4c859eSApple OSS Distributions    f.write(data_ptr[:4096])
120*0f4c859eSApple OSS Distributions```
121*0f4c859eSApple OSS Distributions
122*0f4c859eSApple OSS DistributionsAnd instead use:
123*0f4c859eSApple OSS Distributions
124*0f4c859eSApple OSS Distributions```
125*0f4c859eSApple OSS Distributionsfrom core.io import SBProcessRawIO
126*0f4c859eSApple OSS Distributionsimport shutil
127*0f4c859eSApple OSS Distributions
128*0f4c859eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, 4096)
129*0f4c859eSApple OSS Distributionswith open(filepath, 'wb') as f:
130*0f4c859eSApple OSS Distributions    shutil.copyfileobj(io_access, f)
131*0f4c859eSApple OSS Distributions```
132*0f4c859eSApple OSS Distributions
133*0f4c859eSApple OSS DistributionsOr, if you're in a hurry:
134*0f4c859eSApple OSS Distributions
135*0f4c859eSApple OSS Distributions```
136*0f4c859eSApple OSS Distributionserr = lldb.SBError()
137*0f4c859eSApple OSS Distributionsmy_data = LazyTarget.GetProcess().ReadMemory(start_addr, length, err)
138*0f4c859eSApple OSS Distributionsif err.Success():
139*0f4c859eSApple OSS Distributions    # Use my precious data
140*0f4c859eSApple OSS Distributions    pass
141*0f4c859eSApple OSS Distributions```
142*0f4c859eSApple OSS Distributions
143*0f4c859eSApple OSS DistributionsFor small semi-contiguous chunks, you can map the whole region and access random chunks from it like so:
144*0f4c859eSApple OSS Distributions
145*0f4c859eSApple OSS Distributions```
146*0f4c859eSApple OSS Distributionsfrom core.io import SBProcessRawIO
147*0f4c859eSApple OSS Distributions
148*0f4c859eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, size)
149*0f4c859eSApple OSS Distributionsio_access.seek(my_struct_offset)
150*0f4c859eSApple OSS Distributionsmy_struct_contents = io_access.read(my_struct_size)
151*0f4c859eSApple OSS Distributions```
152*0f4c859eSApple OSS Distributions
153*0f4c859eSApple OSS DistributionsNot only that, but you can also tack on a BufferedRandom class on top of the SBProcessRawIO instance, which
154*0f4c859eSApple OSS Distributionsprovides you with buffering (aka caching) in case your random small chunk accesses are repeated:
155*0f4c859eSApple OSS Distributions
156*0f4c859eSApple OSS Distributions```
157*0f4c859eSApple OSS Distributionsfrom core.io import SBProcessRawIO
158*0f4c859eSApple OSS Distributionsfrom io import BufferedRandom
159*0f4c859eSApple OSS Distributions
160*0f4c859eSApple OSS Distributionsio_access = SBProcessRawIO(LazyTarget.GetProcess(), start_addr, size)
161*0f4c859eSApple OSS Distributionsbuffered_io = BufferedRandom(io_access)
162*0f4c859eSApple OSS Distributions# And then use buffered_io for your accesses
163*0f4c859eSApple OSS Distributions```
164*0f4c859eSApple OSS Distributions
165*0f4c859eSApple OSS Distributions### Encoding data to strings and back
166*0f4c859eSApple OSS Distributions
167*0f4c859eSApple OSS DistributionsThe simplest solution is to use **six** library and one of the functions like:
168*0f4c859eSApple OSS Distributions
169*0f4c859eSApple OSS Distributions```
170*0f4c859eSApple OSS Distributionsmystring = six.ensure_str(object)
171*0f4c859eSApple OSS Distributions```
172*0f4c859eSApple OSS Distributions
173*0f4c859eSApple OSS DistributionsThis ensures the resulting value is a native string. It deals with Unicode in Python 2 automatically.
174*0f4c859eSApple OSS DistributionsThe six library is still required even if data is encoding manually, since it converts types.
175*0f4c859eSApple OSS Distributions
176*0f4c859eSApple OSS Distributions```
177*0f4c859eSApple OSS Distributionsfrom builtins import bytes
178*0f4c859eSApple OSS Distributionsstr = six.ensure_str(bytes.decode('utf-8'))
179*0f4c859eSApple OSS Distributions```
180*0f4c859eSApple OSS Distributions
181*0f4c859eSApple OSS DistributionsWhen converting data to a string, add an encoding type so Python knows how handle raw bytes. In most
182*0f4c859eSApple OSS Distributionscases **utf-8** will work but be careful to be sure that the encoding matches your data.
183*0f4c859eSApple OSS Distributions
184*0f4c859eSApple OSS DistributionsThere are two options to consider when trying to get a string out of the raw data without knowing if
185*0f4c859eSApple OSS Distributionsthey are valid string or not:
186*0f4c859eSApple OSS Distributions
187*0f4c859eSApple OSS Distributions* **lossy conversion** - escapes all non-standard characters in form of ‘\xNNN’
188*0f4c859eSApple OSS Distributions* **lossless conversion** - maps invalid characters to special unicode range so it can reconstruct
189*0f4c859eSApple OSS Distributionsthe string precisely
190*0f4c859eSApple OSS Distributions
191*0f4c859eSApple OSS DistributionsWhich to use depends on the transformation goals. The lossy conversion produces a printable string
192*0f4c859eSApple OSS Distributionswith strange characters in it. The lossless option is meant to be used when a string is only a transport
193*0f4c859eSApple OSS Distributionsmechanism and needs to be converted back to original values later.
194*0f4c859eSApple OSS Distributions
195*0f4c859eSApple OSS DistributionsSwitch the method by using `errors` handler during conversion:
196*0f4c859eSApple OSS Distributions
197*0f4c859eSApple OSS Distributions```
198*0f4c859eSApple OSS Distributions# Lossy escapes invalid chars
199*0f4c859eSApple OSS Distributionsb.decode('utf-8', errors='`backslashreplace'`)
200*0f4c859eSApple OSS Distributions# Lossy removes invalid chars
201*0f4c859eSApple OSS Distributionsb.decode('utf-8', errors='ignore')
202*0f4c859eSApple OSS Distributions# Loss-less but may likely fail to print()
203*0f4c859eSApple OSS Distributionsb.decode('utf-8', errors='surrogateescape')
204*0f4c859eSApple OSS Distributions```
205*0f4c859eSApple OSS Distributions
206*0f4c859eSApple OSS Distributions## Handling numbers
207*0f4c859eSApple OSS Distributions
208*0f4c859eSApple OSS DistributionsNumeric types are incompatible between Python 2 and 3:
209*0f4c859eSApple OSS Distributions
210*0f4c859eSApple OSS Distributions* **long** is not available in Python 3.
211*0f4c859eSApple OSS Distributions* **int** is the only integral type in Python 3 and hasunlimited precission (but 32-bits in Python 2).
212*0f4c859eSApple OSS Distributions
213*0f4c859eSApple OSS DistributionsThis creates all sorts of issues with macros. Follow these rules to make integral types compatible
214*0f4c859eSApple OSS Distributionsin both modes:
215*0f4c859eSApple OSS Distributions
216*0f4c859eSApple OSS Distributions* Do not use **long** — replace it with **int**.
217*0f4c859eSApple OSS Distributions* When using the **value** class, types will be promoted to **long** as there is special number
218*0f4c859eSApple OSS Distributionshandling in the xnu macro library. Remaining code should be reviewed and fixed, if appropriate.
219*0f4c859eSApple OSS Distributions* Avoid relying on sign extension.
220*0f4c859eSApple OSS Distributions* Always switch Python to use Python 3 division, where `/` converts to floating point and does
221*0f4c859eSApple OSS Distributionsa fractional division `//` is a floor division (like integers in C):
222*0f4c859eSApple OSS Distributions   `from __future__ import division
223*0f4c859eSApple OSS Distributions   `
224*0f4c859eSApple OSS Distributions* Use division operators according to Python 3 rules.
225*0f4c859eSApple OSS Distributions
226*0f4c859eSApple OSS Distributions### Common integer representation
227*0f4c859eSApple OSS Distributions
228*0f4c859eSApple OSS DistributionsThe goal is to always use Python 3’s integer handling, which means using **int** everywhere.
229*0f4c859eSApple OSS Distributions
230*0f4c859eSApple OSS Distributionsxnu’s macros provide a custom integer type called **valueint** that is a replacement for **int**
231*0f4c859eSApple OSS Distributionsin the Python 2 runtime. That means it behaves almost like **int** from Python 3. When importing
232*0f4c859eSApple OSS Distributionsfrom macros this type replaces any use of **int**:
233*0f4c859eSApple OSS Distributions
234*0f4c859eSApple OSS Distributions```
235*0f4c859eSApple OSS Distributions# Replaces all int()s to be valueint
236*0f4c859eSApple OSS Distributionsfrom xnu import *
237*0f4c859eSApple OSS Distributionsfrom xnu import int
238*0f4c859eSApple OSS Distributions
239*0f4c859eSApple OSS Distributions# Does not replace int()s
240*0f4c859eSApple OSS Distributionsimport xnu
241*0f4c859eSApple OSS Distributionsfrom xnu import a, b, c
242*0f4c859eSApple OSS Distributions```
243*0f4c859eSApple OSS Distributions
244*0f4c859eSApple OSS DistributionsAvoid using `from builtins import int` suggested on the internet. It does not work correctly with
245*0f4c859eSApple OSS Distributionsxnu’s **value** class. The **valueint** class inherits from **newint** and fixes problematic behavior.
246*0f4c859eSApple OSS Distributions
247*0f4c859eSApple OSS DistributionsThis impacts the way an object is checked for being an integer. Be careful about following constructs:
248*0f4c859eSApple OSS Distributions
249*0f4c859eSApple OSS Distributions```
250*0f4c859eSApple OSS Distributions# BAD: generally not a good way to do type checking in Python
251*0f4c859eSApple OSS Distributionsif type(obj) is int:
252*0f4c859eSApple OSS Distributions
253*0f4c859eSApple OSS Distributions# BAD: int may have been replaced with valueint.
254*0f4c859eSApple OSS Distributionsif isinstance(obj, int):
255*0f4c859eSApple OSS Distributions```
256*0f4c859eSApple OSS Distributions
257*0f4c859eSApple OSS DistributionsInstead, use the base integral type:
258*0f4c859eSApple OSS Distributions
259*0f4c859eSApple OSS Distributions```
260*0f4c859eSApple OSS Distributionsif isinstance(obj, numbers.Integral):
261*0f4c859eSApple OSS Distributions```
262*0f4c859eSApple OSS Distributions
263*0f4c859eSApple OSS Distributions### Dealing with signed numbers
264*0f4c859eSApple OSS Distributions
265*0f4c859eSApple OSS DistributionsOriginal code was using two operators to convert **value** class instance to number:
266*0f4c859eSApple OSS Distributions
267*0f4c859eSApple OSS Distributions* **__int__** produced **int** and was either signed or unsigned based on underlying SBType.
268*0f4c859eSApple OSS Distributions* **__long__** was always signed.
269*0f4c859eSApple OSS Distributions
270*0f4c859eSApple OSS DistributionsThis is confusing when dealing with types. Always use **unsigned()** or **signed()** regardless of
271*0f4c859eSApple OSS Distributionswhat the actual underlying type is to ensure that macros use the correct semantics.
272*0f4c859eSApple OSS Distributions
273*0f4c859eSApple OSS Distributions### Dividing numbers
274*0f4c859eSApple OSS Distributions
275*0f4c859eSApple OSS DistributionsPython 2’s **/** operator has two behaviors depending on the types of its arguments (**float** vs. **int**).
276*0f4c859eSApple OSS DistributionsAlways use Python 3’s division operator:
277*0f4c859eSApple OSS Distributions
278*0f4c859eSApple OSS Distributions```
279*0f4c859eSApple OSS Distributions# Switch compiler to use Python 3 semantics
280*0f4c859eSApple OSS Distributionsfrom __future__ import division
281*0f4c859eSApple OSS Distributions
282*0f4c859eSApple OSS Distributionsfloat_val = a / b  # This becomes true, fractional division that yields float
283*0f4c859eSApple OSS Distributionsfloor_div = a // b # This is floor division, like C
284*0f4c859eSApple OSS Distributions```
285*0f4c859eSApple OSS Distributions
286*0f4c859eSApple OSS DistributionsIf the original behavior is required, use **old_div** to get Python 2 behavior:
287*0f4c859eSApple OSS Distributions
288*0f4c859eSApple OSS Distributions```
289*0f4c859eSApple OSS Distributionsfrom past.utils import old_div
290*0f4c859eSApple OSS Distributions
291*0f4c859eSApple OSS Distributionsvalue = old_div(a, b)     # Matches Python 2 semantics
292*0f4c859eSApple OSS Distributions```
293*0f4c859eSApple OSS Distributions
294*0f4c859eSApple OSS DistributionsIf this isn’t handled correctly, `format` will complain that a float value is being passed to
295*0f4c859eSApple OSS Distributionsa non-float formatting character. Automated scripts that convert from Python 2 to 3 tend to use
296*0f4c859eSApple OSS Distributions**old_div** during porting. In most cases that is not required. For kernel debugging and integer
297*0f4c859eSApple OSS Distributionstypes, `//` is used commonly to match the C’s division behavior for integers.
298*0f4c859eSApple OSS Distributions
299*0f4c859eSApple OSS Distributions## Testing changes
300*0f4c859eSApple OSS Distributions
301*0f4c859eSApple OSS DistributionsThere is no perfect test suite to check that macros are producing a correct value compared to what
302*0f4c859eSApple OSS Distributionsthe debugger sees in a target.
303*0f4c859eSApple OSS Distributions
304*0f4c859eSApple OSS DistributionsBe careful when touching common framework code. For larger changes, ask the Platform Triage team to
305*0f4c859eSApple OSS Distributionsvalidate that the changes work in their environment before integration.
306*0f4c859eSApple OSS Distributions
307*0f4c859eSApple OSS Distributions### Coding style
308*0f4c859eSApple OSS Distributions
309*0f4c859eSApple OSS DistributionsUse a static analyzer like **pylint** or **flake8** to check the macro source code:
310*0f4c859eSApple OSS Distributions
311*0f4c859eSApple OSS Distributions```
312*0f4c859eSApple OSS Distributions# Python 2
313*0f4c859eSApple OSS Distributions$ pip install --user pylint flake8
314*0f4c859eSApple OSS Distributions
315*0f4c859eSApple OSS Distributions# Python 3
316*0f4c859eSApple OSS Distributions$ pip install --user pylint flake8
317*0f4c859eSApple OSS Distributions
318*0f4c859eSApple OSS Distributions# Run the lint either by setting your path to point to one of the runtimes
319*0f4c859eSApple OSS Distributions# or through python
320*0f4c859eSApple OSS Distributions$ python2 -m pylint <src files/dirs>
321*0f4c859eSApple OSS Distributions$ python3 -m pylint <src files/dirs>
322*0f4c859eSApple OSS Distributions$ python2 -m flake8 <src files/dirs>
323*0f4c859eSApple OSS Distributions$ python3 -m flake8 <src files/dirs>
324*0f4c859eSApple OSS Distributions```
325*0f4c859eSApple OSS Distributions
326*0f4c859eSApple OSS Distributions### Correctness
327*0f4c859eSApple OSS Distributions
328*0f4c859eSApple OSS DistributionsEnsure the macro matches what LLDB returns from the REPL. For example, compare `showproc(xxx)` with `p/x *(proc_t)xxx`.
329*0f4c859eSApple OSS Distributions
330*0f4c859eSApple OSS Distributions```
331*0f4c859eSApple OSS Distributions# 1. Run LLDB with debug options set
332*0f4c859eSApple OSS Distributions$ DEBUG_XNU_LLDBMACROS=1 LLDB_DEFAULT_PYTHON_VERSION=2 xcrun -sdk <sdk> lldb -c core <dsympath>/mach_kernel
333*0f4c859eSApple OSS Distributions
334*0f4c859eSApple OSS Distributions# 2. Optionally load modified operating system plugin
335*0f4c859eSApple OSS Distributions(lldb) settings set target.process.python-os-plugin-path <srcpath>/tools/lldbmacros/core/operating_system.py
336*0f4c859eSApple OSS Distributions
337*0f4c859eSApple OSS Distributions# 3. Load modified scripts
338*0f4c859eSApple OSS Distributions(lldb) command script import <srcpath>/tools/lldbmacros/xnu.py
339*0f4c859eSApple OSS Distributions
340*0f4c859eSApple OSS Distributions# 4. Exercise macros
341*0f4c859eSApple OSS Distributions```
342*0f4c859eSApple OSS Distributions
343*0f4c859eSApple OSS DistributionsDepending on the change, test other targets and architectures (for instance, both Astris and KDP).
344*0f4c859eSApple OSS Distributions
345*0f4c859eSApple OSS Distributions### Regression
346*0f4c859eSApple OSS Distributions
347*0f4c859eSApple OSS DistributionsThis is simpler than previous step because the goal is to ensure behavior has not changed.
348*0f4c859eSApple OSS DistributionsYou can speed up few things by using local symbols:
349*0f4c859eSApple OSS Distributions
350*0f4c859eSApple OSS Distributions```
351*0f4c859eSApple OSS Distributions# 1. Get a coredump from a device and kernel UUID
352*0f4c859eSApple OSS Distributions# 2. Grab symbols with dsymForUUID
353*0f4c859eSApple OSS Distributions$ dsymForUUID --nocache --copyExecutable --copyDestination <dsym path>
354*0f4c859eSApple OSS Distributions
355*0f4c859eSApple OSS Distributions# 3. Run lldb with local symbols to avoid dsymForUUID NFS
356*0f4c859eSApple OSS Distributions
357*0f4c859eSApple OSS Distributions$ xcrun -sdk <sdk> lldb -c core <dsym_path>/<kernel image>
358*0f4c859eSApple OSS Distributions```
359*0f4c859eSApple OSS Distributions
360*0f4c859eSApple OSS DistributionsThe actual steps are identical to previous testing. Run of a macro to different file with `-o <outfile>`
361*0f4c859eSApple OSS Distributionsoption. Then run `diff` on the outputs of the baseline and both Python 2 and 3:
362*0f4c859eSApple OSS Distributions
363*0f4c859eSApple OSS Distributions* No environment variables to get baseline
364*0f4c859eSApple OSS Distributions* Python 2 with changes
365*0f4c859eSApple OSS Distributions* Python 3 with changes
366*0f4c859eSApple OSS Distributions
367*0f4c859eSApple OSS DistributionsThere may be different ordering of elements based on internal implementation differences of each
368*0f4c859eSApple OSS DistributionsPython runtime. Some macros produce files — check the actual file contents.
369*0f4c859eSApple OSS Distributions
370*0f4c859eSApple OSS DistributionsIt’s difficult to make this automated:
371*0f4c859eSApple OSS Distributions
372*0f4c859eSApple OSS Distributions* Some macros needs arguments which must be found in a core file.
373*0f4c859eSApple OSS Distributions* Some macros take a long time to run against a target (more than 30 minutes). Instead, a core dump
374*0f4c859eSApple OSS Distributions  should be taken and then inspected afterwards, but this ties up a lab device for the duration of the
375*0f4c859eSApple OSS Distributions  test.
376*0f4c859eSApple OSS Distributions* Even with coredumps, testing the macros takes too long in our automation system and triggers the
377*0f4c859eSApple OSS Distributions  failsafe timeout.
378*0f4c859eSApple OSS Distributions
379*0f4c859eSApple OSS Distributions### Code coverage
380*0f4c859eSApple OSS Distributions
381*0f4c859eSApple OSS DistributionsUse code coverage to check which parts of macros have actually been tested.
382*0f4c859eSApple OSS DistributionsInstall **coverage** lib with:
383*0f4c859eSApple OSS Distributions
384*0f4c859eSApple OSS Distributions```
385*0f4c859eSApple OSS Distributions$ pip install --user coverage
386*0f4c859eSApple OSS Distributions$ pip3 install --user coverage
387*0f4c859eSApple OSS Distributions```
388*0f4c859eSApple OSS Distributions
389*0f4c859eSApple OSS DistributionsThen collect coverage:.
390*0f4c859eSApple OSS Distributions
391*0f4c859eSApple OSS Distributions```
392*0f4c859eSApple OSS Distributions# 1. Start LLDB with your macros as described above.
393*0f4c859eSApple OSS Distributions
394*0f4c859eSApple OSS Distributions# 2. Load and start code coverage recording.
395*0f4c859eSApple OSS Distributions(lldb) script import coverage
396*0f4c859eSApple OSS Distributions(lldb) script cov = coverage.Coverage()
397*0f4c859eSApple OSS Distributions(lldb) script cov.start()
398*0f4c859eSApple OSS Distributions
399*0f4c859eSApple OSS Distributions# 3. Do the testing.
400*0f4c859eSApple OSS Distributions
401*0f4c859eSApple OSS Distributions# 4. Collect the coverage.
402*0f4c859eSApple OSS Distributions(lldb) script cov.stop()
403*0f4c859eSApple OSS Distributions(lldb) script cov.save()
404*0f4c859eSApple OSS Distributions```
405*0f4c859eSApple OSS Distributions
406*0f4c859eSApple OSS DistributionsYou can override the default file (*.coverage*) by adding an additional environment variable to LLDB:
407*0f4c859eSApple OSS Distributions
408*0f4c859eSApple OSS Distributions```
409*0f4c859eSApple OSS Distributions$ env COVERAGE_FILE="${OUTDIR}/.coverage.mytest.py2" # usual LLDB command line
410*0f4c859eSApple OSS Distributions```
411*0f4c859eSApple OSS Distributions
412*0f4c859eSApple OSS DistributionsCombine coverage from multiple files:
413*0f4c859eSApple OSS Distributions
414*0f4c859eSApple OSS Distributions```
415*0f4c859eSApple OSS Distributions# Point PATH to local python where coverage is installed.
416*0f4c859eSApple OSS Distributions$ export PATH="$HOME/Library/Python/3.8/bin:$PATH"
417*0f4c859eSApple OSS Distributions
418*0f4c859eSApple OSS Distributions# Use --keep to avoid deletion of input files after merge.
419*0f4c859eSApple OSS Distributions$ coverage combine --keep <list of .coverage files or dirs to scan>
420*0f4c859eSApple OSS Distributions
421*0f4c859eSApple OSS Distributions# Get HTML report or use other subcommands to inspect.
422*0f4c859eSApple OSS Distributions$ coverage html
423*0f4c859eSApple OSS Distributions```
424*0f4c859eSApple OSS Distributions
425*0f4c859eSApple OSS DistributionsIt is possible to start coverage collection **before** importing the operating system library and
426*0f4c859eSApple OSS Distributionsloading macros to check code run during bootstrapping.
427*0f4c859eSApple OSS Distributions
428*0f4c859eSApple OSS Distributions### Performance testing
429*0f4c859eSApple OSS Distributions
430*0f4c859eSApple OSS DistributionsSome macros can run for a long time. Some code may be costly even if it looks simple because objects
431*0f4c859eSApple OSS Distributionsaren’t cached or too many temporary objects are created. Simple profiling is similar to collecting
432*0f4c859eSApple OSS Distributionscode coverage.
433*0f4c859eSApple OSS Distributions
434*0f4c859eSApple OSS DistributionsFirst setup your environment:
435*0f4c859eSApple OSS Distributions
436*0f4c859eSApple OSS Distributions```
437*0f4c859eSApple OSS Distributions# Install gprof2dot
438*0f4c859eSApple OSS Distributions$ python3 -m pip install gprof2dot
439*0f4c859eSApple OSS Distributions# Install graphviz
440*0f4c859eSApple OSS Distributions$ brew install graphviz
441*0f4c859eSApple OSS Distributions```
442*0f4c859eSApple OSS Distributions
443*0f4c859eSApple OSS DistributionsThen to profile commands, follow this sequence:
444*0f4c859eSApple OSS Distributions
445*0f4c859eSApple OSS Distributions```
446*0f4c859eSApple OSS Distributions(lldb) xnudebug profile /tmp/macro.prof showcurrentstacks
447*0f4c859eSApple OSS Distributions[... command outputs ...]
448*0f4c859eSApple OSS Distributions
449*0f4c859eSApple OSS Distributions   Ordered by: cumulative time
450*0f4c859eSApple OSS Distributions   List reduced from 468 to 30 due to restriction <30>
451*0f4c859eSApple OSS Distributions
452*0f4c859eSApple OSS Distributions   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
453*0f4c859eSApple OSS Distributions   [... profiling output ...]
454*0f4c859eSApple OSS Distributions
455*0f4c859eSApple OSS DistributionsProfile info saved to "/tmp/macro.prof"
456*0f4c859eSApple OSS Distributions```
457*0f4c859eSApple OSS Distributions
458*0f4c859eSApple OSS DistributionsThen to visualize callgraphs in context, in a separate shell:
459*0f4c859eSApple OSS Distributions
460*0f4c859eSApple OSS Distributions```
461*0f4c859eSApple OSS Distributions# Now convert the file to a colored SVG call graph
462*0f4c859eSApple OSS Distributions$ python3 -m gprof2dot -f pstats /tmp/macro.prof -o /tmp/call.dot
463*0f4c859eSApple OSS Distributions$ dot -O -T svg /tmp/call.dot
464*0f4c859eSApple OSS Distributions
465*0f4c859eSApple OSS Distributions# and view it in your favourite viewer
466*0f4c859eSApple OSS Distributions$ open /tmp/call.dot.svg
467*0f4c859eSApple OSS Distributions```
468*0f4c859eSApple OSS Distributions
469*0f4c859eSApple OSS Distributions## Debugging your changes
470*0f4c859eSApple OSS Distributions
471*0f4c859eSApple OSS DistributionsYES, It is possible to use a debugger to debug your code!
472*0f4c859eSApple OSS Distributions
473*0f4c859eSApple OSS DistributionsThe steps are similar to testing techniques described above (use scripting interactive mode). There is no point to
474*0f4c859eSApple OSS Distributionsdocument the debugger itself. Lets focus on how to use it on a real life example. The debugger used here is PDB which
475*0f4c859eSApple OSS Distributionsis part of Python installation so works out of the box.
476*0f4c859eSApple OSS Distributions
477*0f4c859eSApple OSS DistributionsProblem: Something wrong is going on with addkext macro. What now?
478*0f4c859eSApple OSS Distributions
479*0f4c859eSApple OSS Distributions    (lldb) addkext -N com.apple.driver.AppleT8103PCIeC
480*0f4c859eSApple OSS Distributions    Failed to read MachO for address 18446741875027613136 errormessage: seek to offset 2169512 is outside window [0, 1310]
481*0f4c859eSApple OSS Distributions    Failed to read MachO for address 18446741875033537424 errormessage: seek to offset 8093880 is outside window [0, 1536]
482*0f4c859eSApple OSS Distributions    Failed to read MachO for address 18446741875033568304 errormessage: seek to offset 8124208 is outside window [0, 1536]
483*0f4c859eSApple OSS Distributions	...
484*0f4c859eSApple OSS Distributions	Fetching dSYM for 049b9a29-2efc-32c0-8a7f-5f29c12b870c
485*0f4c859eSApple OSS Distributions    Adding dSYM (049b9a29-2efc-32c0-8a7f-5f29c12b870c) for /Library/Caches/com.apple.bni.symbols/bursar.apple.com/dsyms/StarE/AppleEmbeddedPCIE/AppleEmbeddedPCIE-502.100.35~3/049B9A29-2EFC-32C0-8A7F-5F29C12B870C/AppleT8103PCIeC
486*0f4c859eSApple OSS Distributions    section '__TEXT' loaded at 0xfffffe001478c780
487*0f4c859eSApple OSS Distributions
488*0f4c859eSApple OSS DistributionsThere is no exception, lot of errors and no output. So what next?
489*0f4c859eSApple OSS DistributionsTry to narrow the problem down to an isolated piece of macro code:
490*0f4c859eSApple OSS Distributions
491*0f4c859eSApple OSS Distributions  1. Try to get values of globals through regular LLDB commands
492*0f4c859eSApple OSS Distributions  2. Use interactive mode and invoke functions with arguments directly.
493*0f4c859eSApple OSS Distributions
494*0f4c859eSApple OSS DistributionsAfter inspecting addkext macro code and calling few functions with arguments directly we can see that there is an
495*0f4c859eSApple OSS Distributionsexception in the end. It was just captured in try/catch block. So the simplified reproducer is:
496*0f4c859eSApple OSS Distributions
497*0f4c859eSApple OSS Distributions    (lldb) script
498*0f4c859eSApple OSS Distributions	>>> import lldb
499*0f4c859eSApple OSS Distributions	>>> import xnu
500*0f4c859eSApple OSS Distributions	>>> err = lldb.SBError()
501*0f4c859eSApple OSS Distributions	>>> data = xnu.LazyTarget.GetProcess().ReadMemory(0xfffffe0014c0f3f0, 0x000000000001b5d0, err)
502*0f4c859eSApple OSS Distributions    >>> m = macho.MemMacho(data, len(data))
503*0f4c859eSApple OSS Distributions    Traceback (most recent call last):
504*0f4c859eSApple OSS Distributions      File "<console>", line 1, in <module>
505*0f4c859eSApple OSS Distributions      File ".../lldbmacros/macho.py", line 91, in __init__
506*0f4c859eSApple OSS Distributions        self.load(fp)
507*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/MachO.py", line 133, in load
508*0f4c859eSApple OSS Distributions        self.load_header(fh, 0, size)
509*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/MachO.py", line 168, in load_header
510*0f4c859eSApple OSS Distributions        hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian)
511*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/MachO.py", line 209, in __init__
512*0f4c859eSApple OSS Distributions        self.load(fh)
513*0f4c859eSApple OSS Distributions      File ".../lldbmacros/macho.py", line 23, in new_load
514*0f4c859eSApple OSS Distributions        _old_MachOHeader_load(s, fh)
515*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/MachO.py", line 287, in load
516*0f4c859eSApple OSS Distributions        fh.seek(seg.offset)
517*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/util.py", line 91, in seek
518*0f4c859eSApple OSS Distributions        self._checkwindow(seekto, "seek")
519*0f4c859eSApple OSS Distributions      File ".../site-packages/macholib/util.py", line 76, in _checkwindow
520*0f4c859eSApple OSS Distributions        raise IOError(
521*0f4c859eSApple OSS Distributions    OSError: seek to offset 9042440 is outside window [0, 112080]
522*0f4c859eSApple OSS Distributions
523*0f4c859eSApple OSS DistributionsClearly an external library is involved and execution flow jumps between dSYM and the library few times.
524*0f4c859eSApple OSS DistributionsLets try to look around with a debugger.
525*0f4c859eSApple OSS Distributions
526*0f4c859eSApple OSS Distributions    (lldb) script
527*0f4c859eSApple OSS Distributions	# Prepare data variable as described above.
528*0f4c859eSApple OSS Distributions
529*0f4c859eSApple OSS Distributions	# Run last statement with debugger.
530*0f4c859eSApple OSS Distributions	>>> import pdb
531*0f4c859eSApple OSS Distributions	>>> pdb.run('m = macho.MemMacho(data, len(data))', globals(), locals())
532*0f4c859eSApple OSS Distributions	> <string>(1)<module>()
533*0f4c859eSApple OSS Distributions
534*0f4c859eSApple OSS Distributions	# Show debugger's help
535*0f4c859eSApple OSS Distributions	(Pdb) help
536*0f4c859eSApple OSS Distributions
537*0f4c859eSApple OSS DistributionsIt is not possible to break on exception. Python uses them a lot so it is better to put a breakpoint to source
538*0f4c859eSApple OSS Distributionscode. This puts breakpoint on the IOError exception mentioned above.
539*0f4c859eSApple OSS Distributions
540*0f4c859eSApple OSS Distributions	(Pdb) break ~/Library/Python/3.8/lib/python/site-packages/macholib/util.py:76
541*0f4c859eSApple OSS Distributions    Breakpoint 4 at ~/Library/Python/3.8/lib/python/site-packages/macholib/util.py:76
542*0f4c859eSApple OSS Distributions
543*0f4c859eSApple OSS DistributionsYou can now single step or continue the execution as usuall for a debugger.
544*0f4c859eSApple OSS Distributions
545*0f4c859eSApple OSS Distributions    (Pdb) cont
546*0f4c859eSApple OSS Distributions    > /Users/tjedlicka/Library/Python/3.8/lib/python/site-packages/macholib/util.py(76)_checkwindow()
547*0f4c859eSApple OSS Distributions    -> raise IOError(
548*0f4c859eSApple OSS Distributions    (Pdb) bt
549*0f4c859eSApple OSS Distributions      /Volumes/.../Python3.framework/Versions/3.8/lib/python3.8/bdb.py(580)run()
550*0f4c859eSApple OSS Distributions    -> exec(cmd, globals, locals)
551*0f4c859eSApple OSS Distributions      <string>(1)<module>()
552*0f4c859eSApple OSS Distributions      /Volumes/...dSYM/Contents/Resources/Python/lldbmacros/macho.py(91)__init__()
553*0f4c859eSApple OSS Distributions    -> self.load(fp)
554*0f4c859eSApple OSS Distributions      /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(133)load()
555*0f4c859eSApple OSS Distributions    -> self.load_header(fh, 0, size)
556*0f4c859eSApple OSS Distributions      /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(168)load_header()
557*0f4c859eSApple OSS Distributions    -> hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian)
558*0f4c859eSApple OSS Distributions      /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(209)__init__()
559*0f4c859eSApple OSS Distributions    -> self.load(fh)
560*0f4c859eSApple OSS Distributions      /Volumes/...dSYM/Contents/Resources/Python/lldbmacros/macho.py(23)new_load()
561*0f4c859eSApple OSS Distributions    -> _old_MachOHeader_load(s, fh)
562*0f4c859eSApple OSS Distributions      /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(287)load()
563*0f4c859eSApple OSS Distributions    -> fh.seek(seg.offset)
564*0f4c859eSApple OSS Distributions      /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/util.py(91)seek()
565*0f4c859eSApple OSS Distributions    -> self._checkwindow(seekto, "seek")
566*0f4c859eSApple OSS Distributions    > /Users/.../Library/Python/3.8/lib/python/site-packages/macholib/util.py(76)_checkwindow()
567*0f4c859eSApple OSS Distributions    -> raise IOError(
568*0f4c859eSApple OSS Distributions
569*0f4c859eSApple OSS Distributions
570*0f4c859eSApple OSS DistributionsNow we can move a frame above and inspect stopped target:
571*0f4c859eSApple OSS Distributions
572*0f4c859eSApple OSS Distributions    # Show current frame arguments
573*0f4c859eSApple OSS Distributions    (Pdb) up
574*0f4c859eSApple OSS Distributions    (Pdb) a
575*0f4c859eSApple OSS Distributions    self = <fileview [0, 112080] <macho.MemFile object at 0x1075cafd0>>
576*0f4c859eSApple OSS Distributions    offset = 9042440
577*0f4c859eSApple OSS Distributions    whence = 0
578*0f4c859eSApple OSS Distributions
579*0f4c859eSApple OSS Distributions    # globals, local or expressons
580*0f4c859eSApple OSS Distributions    (Pdb) p type(seg.offset)
581*0f4c859eSApple OSS Distributions    <class 'macholib.ptypes.p_uint32'>
582*0f4c859eSApple OSS Distributions    (Pdb) p hex(seg.offset)
583*0f4c859eSApple OSS Distributions    '0x89fa08'
584*0f4c859eSApple OSS Distributions
585*0f4c859eSApple OSS Distributions    # Find attributes of a Python object.
586*0f4c859eSApple OSS Distributions    (Pdb) p dir(section_cls)
587*0f4c859eSApple OSS Distributions    ['__class__', '__cmp__', ... ,'reserved3', 'sectname', 'segname', 'size', 'to_fileobj', 'to_mmap', 'to_str']
588*0f4c859eSApple OSS Distributions    (Pdb) p section_cls.sectname
589*0f4c859eSApple OSS Distributions    <property object at 0x1077bbef0>
590*0f4c859eSApple OSS Distributions
591*0f4c859eSApple OSS DistributionsUnfortunately everything looks correct but there is actually one ineteresting frame in the stack. The one which
592*0f4c859eSApple OSS Distributionsprovides the offset to the seek method. Lets see where we are in the source code.
593*0f4c859eSApple OSS Distributions
594*0f4c859eSApple OSS Distributions    (Pdb) up
595*0f4c859eSApple OSS Distributions    > /Users/tjedlicka/Library/Python/3.8/lib/python/site-packages/macholib/MachO.py(287)load()
596*0f4c859eSApple OSS Distributions    -> fh.seek(seg.offset)
597*0f4c859eSApple OSS Distributions    (Pdb) list
598*0f4c859eSApple OSS Distributions    282  	                        not_zerofill = (seg.flags & S_ZEROFILL) != S_ZEROFILL
599*0f4c859eSApple OSS Distributions    283  	                        if seg.offset > 0 and seg.size > 0 and not_zerofill:
600*0f4c859eSApple OSS Distributions    284  	                            low_offset = min(low_offset, seg.offset)
601*0f4c859eSApple OSS Distributions    285  	                        if not_zerofill:
602*0f4c859eSApple OSS Distributions    286  	                            c = fh.tell()
603*0f4c859eSApple OSS Distributions    287  ->	                            fh.seek(seg.offset)
604*0f4c859eSApple OSS Distributions    288  	                            sd = fh.read(seg.size)
605*0f4c859eSApple OSS Distributions    289  	                            seg.add_section_data(sd)
606*0f4c859eSApple OSS Distributions    290  	                            fh.seek(c)
607*0f4c859eSApple OSS Distributions    291  	                        segs.append(seg)
608*0f4c859eSApple OSS Distributions    292  	                # data is a list of segments
609*0f4c859eSApple OSS Distributions
610*0f4c859eSApple OSS DistributionsRunning debugger on working case and stepping through the load() method shows that this code is not present.
611*0f4c859eSApple OSS DistributionsThat means we are broken by a library update! Older versions of library do not load data for a section.
612