我正在写信,看看你们有没有见过或听说过我将要描述的想法的实现。
我有兴趣为嵌入式目标开发一个printf样式的调试库。目标是非常远程的,我和目标之间的通信带宽预算非常紧张,所以我希望能够以非常有效的格式获得调试消息。
通常,调试语句如下所示:
myDebugLibraryPrintf("Inside loop, processing item %d out of %d.\n", i, numItems);
当然,当它被扩展为文本时,打印的字符串类似于“内部循环,处理项目5中的10个\ n”,总共约42个字节左右。此语句打印出的90%以上的数据是静态的,字面上的 - 在编译时已知。当然,在编译时只知道“5”和“10”。
我想做的是只能回发那两个整数(8个字节而不是42个字节)。一旦我收到了这些数据,我就会有一些“解码器响铃”让我“重新构建”收到的数据并在我的位置打印出完整的调试信息。
我会自动生成“解码器环”(作为构建过程的一部分),在编译时为每个myDebugLibraryPrintf()语句提供一个唯一的ID,并生成一个将这些唯一ID映射到原始格式字符串的表。然后,只要在目标上调用myDebugLibraryPrintf(),它就会传输唯一ID以及格式字符串中看到的任何"%d"
,"%f"
等varargs值,但格式字符串本身是没发送。 (我现在可能只是禁止"%s"
个项目...)回到我的位置,我们将有一个程序查找表中的唯一ID,找到相应的格式字符串,并将其用于重建原始调试消息。
我觉得以前可能有人有这个想法,我想社区中的某个人可能会看到类似的东西(或者甚至知道这样做的开源库)。
约束:
为了澄清,我在这里处理C / C ++,我对printf()的100%完全替换实现不感兴趣 - 像非文字格式字符串,{{1不需要支持(字符串)格式说明符,或者更高级的格式说明符,例如将宽度或精度放在带有%s
的varargs列表中。
我希望在构建过程中自动生成字符串表,以便添加调试不会比添加传统的printf()更多。如果需要超过最小努力量,我项目中的任何人都不会使用它。
在生成字符串表的构建过程中做额外的工作几乎是假定的。幸运的是,我控制了我对使用这个库感兴趣的所有源代码,并且在构建过程中我有很大的灵活性。
谢谢!
答案 0 :(得分:3)
我只看到这个想法是用一组预定义的字符串实现的。代码看起来像debug_print(INSIDE_LOOP_MSG_ID, i, n)
。当开发人员想要添加新消息时,他们必须将新文本放在特定的头文件中并为其提供新的ID。
我认为从正常的印刷语句中动态生成它的想法是一个有趣的挑战。我没有遇到任何现有的实现。
一个想法可能是宏/模板,它将第一个字符串参数转换为hash value at compile time。因此,开发人员编写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);
}
稍微聪明一点,你可以支持多个论点。 检查dissasembly显示所有哈希确实在编译时计算。输出是预期的,没有来自相同字符串的冲突。 (This page确认十六进制对于3.14)是正确的:
Sending 94b7555c 1
Sending 62fce13e 2
Sending 506e9a0c 4048f5c3
现在您需要的是一个文本处理脚本,可以在代码上运行,该代码从Debug_Print中提取字符串,计算哈希值并填充接收方的表格。接收方从Send
调用中获取哈希值,查找与其一起使用的字符串,并将其与参数一起传递给正常的printf调用。
我看到的唯一问题是编译时哈希中的嵌套宏是confusing my refactoring plug-in and killing my IDE responsiveness。禁用加载项会删除该问题。
答案 1 :(得分:1)
我见过在ARM平台上完成类似工作的东西。我相信它被称为“Embedded Trace Macrocell”。一系列宏将TRACE_POWER_SYSTEM_VOLTAGE_REGULATOR_TRIGGER(inputX);
之类的语句转换为两个寄存器写入ETM寄存器。请注意,这只能接受16位,32位和64位整数作为参数。
我们可以使用ARM工具提取这些(带时间戳)缓冲区。然后我们应用一个预先编译的技巧来将第一个(索引)寄存器写入转换为如下所示的输出文件:
timestamp | POWER SYSTEM | VOLTAGE REGULATOR TRIGGER | 0x2380FF23
已检查代码以确定参数的数据类型,因此我们不必费心。它还可以使用“实时”时间戳(而不是自上电后的ms)以及跟踪语句的文件和行号进行注释。
ARM设置为在内部(并且非常快速)存储此循环缓冲区,因此可以在生产中使用它。即使你没有硬件支持,但......这方面的某些方面可以轻松复制。
请注意,分析跟踪时非常重要,您只能使用与设备上运行的代码的特定版本相匹配的“解码”文件。
答案 2 :(得分:0)
我似乎回想起许多用于提取字符串文字的工具,以实现国际化。 GNU字符串可以直接从可执行文件中提取字符串。这应该有助于完成部分任务。
答案 3 :(得分:0)
我有同样的问题我想减小图像尺寸(由于微小的嵌入式闪存)。 我的解决方案是发送文件名和行(应该是14-20字节)并在服务器端有一个源解析器,它将生成实际文本的映射。这样,实际代码将不包含"格式"字符串,但单个"文件名"每个文件的字符串。此外,文件名可以很容易地用枚举替换(不像替换代码中的每个字符串),以减少COMM吞吐量。
我希望示例psaudo代码有助于澄清这个想法:
/* 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;
}