9

我写信是想看看你们中是否有人见过或听说过我将要描述的想法的实现。

我有兴趣为嵌入式目标开发一个 printf 风格的调试库。目标非常遥远,我和目标之间的通信带宽预算非常紧张,所以我希望能够以非常有效的格式获取调试消息。

很多时候,调试语句看起来像下面这样:

myDebugLibraryPrintf("Inside loop, processing item %d out of %d.\n", i, numItems);

当然,当它扩展为文本时,打印的字符串类似于“Inside loop, processing item 5 out of 10.\n”,总共约 42 个字节左右。该语句打印的 90% 以上的数据是静态的、文字的——在编译时就知道了。当然,只有“5”和“10”在编译时是未知的。

我想做的是能够只发回这两个整数(8 个字节而不是 42 个)。一旦我收到该数据,我就会有某种“解码器环”,让我“重构”接收到的数据并在我的位置打印出完整的调试消息。

我将通过在编译时自动(作为构建过程的一部分)为每个 myDebugLibraryPrintf() 语句提供唯一 ID 并生成将这些唯一 ID 映射到原始格式字符串的表来生成“解码器环”。然后,每当在目标上调用 myDebugLibraryPrintf() 时,它都会传输唯一 ID 和格式字符串中看到的任何"%d""%f"等可变参数值,但不传输格式字符串本身。(我"%s"现在可能只是禁止项目......)回到我的位置,我们将有一个程序查找表中的唯一 ID,找到适当的格式字符串,并使用它来重建原始调试消息.

我觉得以前可能有人有过这个想法,我想也许社区中的某个人会看到类似的东西(或者甚至知道这样做的开源库)。

约束:

  • 澄清一下,我在这里处理的是 C/C++,我对 printf() 的 100% 完全替换实现不感兴趣——比如非文字格式字符串、%s(字符串)格式说明符或更高级的东西%*.*d不需要支持格式说明符,例如将宽度或精度放入可变参数列表中。

  • 我希望作为构建过程的一部分自动生成字符串表,这样添加调试所涉及的工作就不会比添加传统的 printf() 更多。如果需要的工作量超过最低限度,我的项目中没有人会使用它。

  • 作为生成字符串表的构建过程的一部分,做额外的工作几乎是假设的。幸运的是,我可以控制我对使用这个库感兴趣的所有源代码,并且我在构建过程中有很大的灵活性。

谢谢!

4

4 回答 4

3

我只看到用一组预定义的字符串实现了这个想法。代码看起来像debug_print(INSIDE_LOOP_MSG_ID, i, n). 当开发人员想要添加新消息时,他们必须将新文本放在特定的头文件中并给它一个新的 ID。

我认为从看起来正常的印刷声明中动态生成它的想法是一个有趣的挑战。我还没有遇到任何现有的实现。

一个想法可能是宏/模板,它在编译时将第一个字符串参数转换为哈希值。所以开发者写了debug_print("test %d",i),它被编译成debug_port_send(0x1d3s, i). 编写一个后处理脚本来提取字符串和散列以供接收端使用应该很简单。(解决哈希冲突的最简单方法是给出错误消息并强制用户稍微改变措辞)。

编辑:
所以我在上面的链接中使用编译时哈希尝试了这个。

#define QQuot_(x) #x
#define QQuote(x) QQuot_(x)
#define Debug_Print(s, v) (Send( CONSTHASH(QQuote(__LINE__)##s), *((long*)&(v))))

void Send(long hash, long value)
{
   printf("Sending %x %x\n", hash, value); //replace with COMMS
}


int main()
{
   int i = 1;
   float f= 3.14f;
   Debug_Print("This is a test %d", i);
   i++;
   Debug_Print("This is a test %d", i);
   Debug_Print("This was test %f", f);
}

再聪明一点,您就可以支持多个参数。检查反汇编表明所有哈希确实是在编译时计算的。输出如预期的那样,没有来自相同字符串的冲突。(此页面确认十六进制对于 3.14 是正确的):

Sending 94b7555c 1
Sending 62fce13e 2
Sending 506e9a0c 4048f5c3

您现在需要的只是一个文本处理脚本,该脚本可以在从 Debug_Print 中提取字符串、计算哈希值并在您的接收者端填充表的代码上运行。接收者从调用中获取一个散列值Send,查找随之而来的字符串,并将其与参数一起传递给正常的 printf 调用。

我看到的唯一问题是编译时哈希中的嵌套宏混淆了我的重构插件并扼杀了我的 IDE 响应能力。禁用加载项消除了该问题。

于 2011-08-02T18:36:15.120 回答
1

我已经看到在 ARM 平台上完成类似的事情。我相信它被称为“嵌入式跟踪宏单元”。一系列宏将语句转换TRACE_POWER_SYSTEM_VOLTAGE_REGULATOR_TRIGGER(inputX);为两个寄存器写入 ETM 寄存器。请注意,这仅接受 16 位、32 位和 64 位整数作为参数。

我们可以使用 ARM 工具来提取这些(时间戳)缓冲区。然后我们应用一个预编译的技巧将第一个(索引)寄存器写入转换为如下所示的输出文件:

timestamp  | POWER SYSTEM    |    VOLTAGE REGULATOR TRIGGER    | 0x2380FF23

已检查代码以确定参数的数据类型,因此我们不必费心。它还可以用“实时”时间戳(而不是自加电以来的毫秒)以及跟踪语句的文件和行号进行注释。

ARM 设置为在内部(并且非常快速)存储此循环缓冲区,因此可以在生产中使用。即使您没有硬件支持,但是……这方面的某些方面可以很容易地复制。

请注意,在分析跟踪时,仅使用与设备上运行的特定代码版本匹配的“解码”文件非常重要。

于 2011-08-10T22:50:54.450 回答
0

我遇到了同样的问题,而且我想减小图像大小(由于嵌入式闪存很小)。我的解决方案是发送文件名和行(应该是 14-20 字节)并在服务器端有一个源解析器,它将生成实际文本的映射。这样,实际代码将不包含“格式”字符串,而是每个文件的单个“文件名”字符串。此外,文件名可以很容易地用枚举替换(与替换代码中的每个字符串不同)以降低 COMM 吞吐量。

我希望示例伪代码将有助于澄清这个想法:

/* target code */
#define PRINT(format,...) send(__FILE__,__LINE__,__VA_ARGS__)
...

/* host code (c++) */
void PrintComm(istream& in)
{
    string fileName;
    int    line,nParams;
    int*   params;
    in>>fileName>>line>>nParams;
    if (nParams>0)
    {
        params = new int[nParams];
        for (int i=0; i<nParams; ++i)
            in>>params[i];
    }
    const char* format = FindFormat(fileName,line);
    ...
    delete[] params;
}
于 2013-07-28T11:32:07.870 回答
0

我似乎想起了许多用于提取字符串文字以实现国际化的工具。GNU 字符串可以直接从可执行文件中提取字符串。这应该有助于完成部分任务。

于 2011-08-10T23:08:17.877 回答