【问题标题】:Declare an lldb summary-string for a sized string type为大小字符串类型声明一个 lldb 摘要字符串
【发布时间】:2016-04-10 18:31:42
【问题描述】:

我想要一个用于 nim 语言的内置字符串类型的格式化程序,但不知何故我无法提供它。 Nim 编译为 c,以及您在此处看到的字符串类型的 c 表示:

#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]; };

这是我在 lldb 会话中尝试过的输出:

(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) 

我根本无法管理,输出将只是被解释为'\0' 终止的 c 字符串的数据

【问题讨论】:

    标签: debugging lldb


    【解决方案1】:

    您尝试过的摘要字符串语法(按设计)不如 C 语法丰富。

    而且由于您使用的是零大小的数组,我认为我们没有任何神奇的规定可以将其视为指向字符串的指针。你可能想提交一个关于它的错误,但在这种情况下,它是否对你有帮助是有争议的。由于您的字符串是长度编码的,因此它实际上并不需要以零结尾,这是 LLDB 开箱即用地知道何时停止读取指向字符的唯一提示。

    在你的情况下,你将不得不求助于 Python 格式化程序

    你需要的东西是:

    • 字符串缓冲区的内存位置
    • 字符串缓冲区的长度
    • 从内存中读取的进程

    这是一个非常小的 Python sn-p 可以做到这一点 - 我也会给你一些增强建议,但让我们从基础开始:

    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())
    

    如您所见,它首先读取长度字段的值; 然后它读取数据缓冲区的地址;然后它使用值来自的过程来读取字符串内容,并在引号中返回它

    现在,这是一个概念证明。如果你在生产环境中使用它,你很快就会遇到一些问题:

    如果你的字符串缓冲区还没有被初始化,并且它说缓冲区的大小是 20 GB 怎么办?您将不得不限制您愿意阅读的数据的大小。对于类似字符串的类型,它具有 (char*, std::string, Swift.String, ...) 的内置知识,LLDB 打印出截断的缓冲区,后跟 ...,例如

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

    如果指向数据的指针无效怎么办?您应该检查 s.GetValueAsUnsigned(0) 实际上不是零 - 如果是,您可能想要打印一条错误消息,例如“空缓冲区”。

    另外,这里我只是传递了一个 SBError,然后我会忽略它 - 最好传递一个然后检查它 总而言之,您最终会得到如下结果:

    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])
    

    一个额外的技巧是 __lldb_init_module 函数——只要你“命令脚本导入”一个 python 文件,这个函数就会被 LLDB 自动调用。这将允许您将“命令脚本导入”添加到您的 ~/.lldbinit 文件中,并自动在所有调试会话中获取格式化程序

    希望这会有所帮助!

    【讨论】:

    • 感谢您的回答,这对我帮助很大。我会尽快尝试。但是在您已经访问了 Sup 成员之后,您正在检查data 的地址为0。数据的地址永远不能是0,因为字符串是直接嵌入在sup之后的,不是指针。
    • 我保持原样,因为这是我在 Stackoverflow 上得到的最佳答案,非常感谢。一切正常,而且解释得很好。
    • Enrico - 这是纯金。谢谢你写这篇文章!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多