Arne
Arne

Reputation: 8481

Declare an lldb summary-string for a sized string type

I would like to have a formatter for the buildin string type of the nim language, but somehow I fail at providing it. Nim compilis to c, and the c representation of the string type you see here:

#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
#  define SEQ_DECL_SIZE /* empty is correct! */
#else
#  define SEQ_DECL_SIZE 1000000
#endif
typedef char NIM_CHAR;
typedef long long int NI64;
typedef NI64 NI;
struct TGenericSeq {NI len; NI reserved; };
struct NimStringDesc {TGenericSeq Sup; NIM_CHAR data[SEQ_DECL_SIZE]; };

and here is the output of what I have tried in the lldb session:

(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
  Sup = (len = 9, reserved = 15)
  data = {}
}
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb)  type summary add --summary-string "${&var[0]%s}" "NIM_CHAR []"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb)  type summary add --summary-string "${var%s}" "NIM_CHAR *"
(lldb) frame variable longstring->data
(NIM_CHAR []) longstring->data = {}
(lldb) frame variable &longstring->data[0]
(NIM_CHAR *) &[0] = 0x00007ffff7f3a060 "9 - 3 - 2"
(lldb) frame variable *longstring
(lldb)  type summary add --summary-string "${var.data%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = NIM_CHAR [] @ 0x7ffff7f3a060
(lldb)  type summary add --summary-string "${&var.data[0]%s}" "NimStringDesc"
(lldb) frame variable *longstring
(NimStringDesc) *longstring = {
  Sup = (len = 9, reserved = 15)
  data = {}
}
(lldb) 

I simply can't manage, that the output will just be data interpreted as a '\0' terminated c-string

Upvotes: 1

Views: 702

Answers (1)

Enrico Granata
Enrico Granata

Reputation: 3329

The summary string syntax you've tried is (by design) not as syntax rich as C.

And since you're using a zero-sized array, I don't think we have any magic provision to treat that as a pointer-to string. You might want to file a bug about it, but in this case, it's arguable whether it would help you. Since your string is length-encoded it doesn't really need to be zero-terminated, and that is the only hint LLDB would understand out of the box to know when to stop reading out of a pointer-to characters.

In your case, you're going to have to resort to Python formatters

The things you need are:

  • the memory location of the string buffer
  • the length of the string buffer
  • a process to read memory out of

This is a very small Python snippet that does it - I'll give you enhancement suggestions as well, but let's start with the basics:

def NimStringSummary(valobj,stuff):
    l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
    s = valobj.GetChildMemberWithName('data').AddressOf()
    return '"%s"'% valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,lldb.SBError())

As you can see, first of all it reads the value of the length field; then it reads the address-of the data buffer; then it uses the process that the value comes from to read the string content, and returns it in quotes

Now, this is a proof of concept. If you used it in production, you'd quickly run into a few issues:

What if your string buffer hasn't been initialized yet, and it says the size of the buffer is 20 gigabytes? You're going to have to limit the size of the data you're willing to read. For string-like types it has builtin knowledge of (char*, std::string, Swift.String, ...) LLDB prints out the truncated buffer followed by ..., e.g.

  (const char*) buffer = "myBufferIsVeryLong"...

What if the pointer to the data is invalid? You should check that s.GetValueAsUnsigned(0) isn't actually zero - if it is you might want to print an error message like "null buffer".

Also, here I just passed an SBError that I then ignore - it would be better to pass one and then check it All in all, you'd end up with something like:

import lldb
import os

def NimStringSummary(valobj,stuff):
    l = valobj.GetChildMemberWithName('Sup').GetChildMemberWithName('len').GetValueAsUnsigned(0)
    if l == 0: return '""'
    if l > 1024: l = 1024
    s = valobj.GetChildMemberWithName('data').AddressOf()
    addr = s.GetValueAsUnsigned(0)
    if addr == 0: return '<null buffer>'
    err = lldb.SBError()
    buf = valobj.process.ReadMemory(s.GetValueAsUnsigned(0),l,err)
    if err.Fail(): return '<error: %s>' % str(err)
    return '"%s"' % buf

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand("type summary add NimStringDesc -F %s.NimStringSummary" % os.path.splitext(os.path.basename(__file__))[0])

The one extra trick is the __lldb_init_module function - this function is automatically called by LLDB whenever you 'command script import' a python file. This will allow you to add the 'command script import ' to your ~/.lldbinit file and automatically get the formatter to be picked up in all debug sessions

Hope this helps!

Upvotes: 2

Related Questions