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