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