xref: /llvm-project/lldb/examples/python/diagnose_nsstring.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1# This implements the "diagnose-nsstring" command, usually installed in the debug session like
2#   command script import lldb.diagnose
3# it is used when NSString summary formatter fails to replicate the logic that went into LLDB making the
4# decisions it did and  providing some useful context information that can
5# be used for improving the formatter
6
7import lldb
8
9
10def read_memory(process, location, size):
11    data = ""
12    error = lldb.SBError()
13    for x in range(0, size - 1):
14        byte = process.ReadUnsignedFromMemory(x + location, 1, error)
15        if error.fail:
16            data = data + "err%s" % "" if x == size - 2 else ":"
17        else:
18            try:
19                data = data + "0x%x" % byte
20                if byte == 0:
21                    data = data + "(\\0)"
22                elif byte == 0xA:
23                    data = data + "(\\a)"
24                elif byte == 0xB:
25                    data = data + "(\\b)"
26                elif byte == 0xC:
27                    data = data + "(\\c)"
28                elif byte == "\n":
29                    data = data + "(\\n)"
30                else:
31                    data = data + "(%s)" % chr(byte)
32                if x < size - 2:
33                    data = data + ":"
34            except Exception as e:
35                print(e)
36    return data
37
38
39def diagnose_nsstring_Command_Impl(debugger, command, result, internal_dict):
40    """
41    A command to diagnose the LLDB NSString data formatter
42    invoke as
43    (lldb) diagnose-nsstring <expr returning NSString>
44    e.g.
45    (lldb) diagnose-nsstring @"Hello world"
46    """
47    target = debugger.GetSelectedTarget()
48    process = target.GetProcess()
49    thread = process.GetSelectedThread()
50    frame = thread.GetSelectedFrame()
51    if not target.IsValid() or not process.IsValid():
52        return "unable to get target/process - cannot proceed"
53    options = lldb.SBExpressionOptions()
54    options.SetFetchDynamicValue()
55    error = lldb.SBError()
56    if frame.IsValid():
57        nsstring = frame.EvaluateExpression(command, options)
58    else:
59        nsstring = target.EvaluateExpression(command, options)
60    print(str(nsstring), file=result)
61    nsstring_address = nsstring.GetValueAsUnsigned(0)
62    if nsstring_address == 0:
63        return "unable to obtain the string - cannot proceed"
64    expression = "\
65struct $__lldb__notInlineMutable {\
66    char* buffer;\
67    signed long length;\
68    signed long capacity;\
69    unsigned int hasGap:1;\
70    unsigned int isFixedCapacity:1;\
71    unsigned int isExternalMutable:1;\
72    unsigned int capacityProvidedExternally:1;\n\
73#if __LP64__\n\
74    unsigned long desiredCapacity:60;\n\
75#else\n\
76    unsigned long desiredCapacity:28;\n\
77#endif\n\
78    void* contentsAllocator;\
79};\
80\
81struct $__lldb__CFString {\
82    void* _cfisa;\
83    uint8_t _cfinfo[4];\
84    uint32_t _rc;\
85    union {\
86        struct __inline1 {\
87            signed long length;\
88        } inline1;\
89        struct __notInlineImmutable1 {\
90            char* buffer;\
91            signed long length;\
92            void* contentsDeallocator;\
93        } notInlineImmutable1;\
94        struct __notInlineImmutable2 {\
95            char* buffer;\
96            void* contentsDeallocator;\
97        } notInlineImmutable2;\
98        struct $__lldb__notInlineMutable notInlineMutable;\
99    } variants;\
100};\
101"
102
103    expression = expression + "*(($__lldb__CFString*) %d)" % nsstring_address
104    # print expression
105    dumped = target.EvaluateExpression(expression, options)
106    print(str(dumped), file=result)
107
108    little_endian = target.byte_order == lldb.eByteOrderLittle
109    ptr_size = target.addr_size
110
111    info_bits = (
112        dumped.GetChildMemberWithName("_cfinfo")
113        .GetChildAtIndex(0 if little_endian else 3)
114        .GetValueAsUnsigned(0)
115    )
116    is_mutable = (info_bits & 1) == 1
117    is_inline = (info_bits & 0x60) == 0
118    has_explicit_length = (info_bits & (1 | 4)) != 4
119    is_unicode = (info_bits & 0x10) == 0x10
120    is_special = (
121        nsstring.GetDynamicValue(lldb.eDynamicCanRunTarget).GetTypeName()
122        == "NSPathStore2"
123    )
124    has_null = (info_bits & 8) == 8
125
126    print(
127        "\nInfo=%d\nMutable=%s\nInline=%s\nExplicit=%s\nUnicode=%s\nSpecial=%s\nNull=%s\n"
128        % (
129            info_bits,
130            "yes" if is_mutable else "no",
131            "yes" if is_inline else "no",
132            "yes" if has_explicit_length else "no",
133            "yes" if is_unicode else "no",
134            "yes" if is_special else "no",
135            "yes" if has_null else "no",
136        ),
137        file=result,
138    )
139
140    explicit_length_offset = 0
141    if not has_null and has_explicit_length and not is_special:
142        explicit_length_offset = 2 * ptr_size
143        if is_mutable and not is_inline:
144            explicit_length_offset = explicit_length_offset + ptr_size
145        elif is_inline:
146            pass
147        elif not is_inline and not is_mutable:
148            explicit_length_offset = explicit_length_offset + ptr_size
149        else:
150            explicit_length_offset = 0
151
152    if explicit_length_offset == 0:
153        print("There is no explicit length marker - skipping this step\n", file=result)
154    else:
155        explicit_length_offset = nsstring_address + explicit_length_offset
156        explicit_length = process.ReadUnsignedFromMemory(
157            explicit_length_offset, 4, error
158        )
159        print(
160            "Explicit length location is at 0x%x - read value is %d\n"
161            % (explicit_length_offset, explicit_length),
162            file=result,
163        )
164
165    if is_mutable:
166        location = 2 * ptr_size + nsstring_address
167        location = process.ReadPointerFromMemory(location, error)
168    elif (
169        is_inline
170        and has_explicit_length
171        and not is_unicode
172        and not is_special
173        and not is_mutable
174    ):
175        location = 3 * ptr_size + nsstring_address
176    elif is_unicode:
177        location = 2 * ptr_size + nsstring_address
178        if is_inline:
179            if not has_explicit_length:
180                print(
181                    "Unicode & Inline & !Explicit is a new combo - no formula for it",
182                    file=result,
183                )
184            else:
185                location += ptr_size
186        else:
187            location = process.ReadPointerFromMemory(location, error)
188    elif is_special:
189        location = nsstring_address + ptr_size + 4
190    elif is_inline:
191        location = 2 * ptr_size + nsstring_address
192        if not has_explicit_length:
193            location += 1
194    else:
195        location = 2 * ptr_size + nsstring_address
196        location = process.ReadPointerFromMemory(location, error)
197    print("Expected data location: 0x%x\n" % (location), file=result)
198    print(
199        "1K of data around location: %s\n" % read_memory(process, location, 1024),
200        file=result,
201    )
202    print(
203        "5K of data around string pointer: %s\n"
204        % read_memory(process, nsstring_address, 1024 * 5),
205        file=result,
206    )
207
208
209def __lldb_init_module(debugger, internal_dict):
210    debugger.HandleCommand(
211        "command script add -o -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring"
212        % __name__
213    )
214    print(
215        'The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.'
216    )
217
218
219__lldb_init_module(lldb.debugger, None)
220__lldb_init_module = None
221