char *str = cJSON_Print(root);
4184ec: 8f9981ec lw t9,-32276(gp)
4184f0: 00000000 nop
4184f4: 0320f809 jalr t9
4184f8: 02002021 move a0,s0
4184fc: 8fbc0010 lw gp,16(sp)
httpnPrintf(reqId, strlen(str) + 1, "%s\n", str);
418500: 00402021 move a0,v0
418504: 8f999fe4 lw t9,-24604(gp)
418508: 00000000 nop
41850c: 0320f809 jalr t9
418510: 00408821 move s1,v0
418514: 8fbc0010 lw gp,16(sp)
418518: 3c060062 lui a2,0x62
41851c: 02203821 move a3,s1
418520: 8f9981f0 lw t9,-32272(gp)
418524: 24450001 addiu a1,v0,1
418528: 24c6cdd8 addiu a2,a2,-12840
41852c: 0320f809 jalr t9
418530: 02402021 move a0,s2
418534: 8fbc0010 lw gp,16(sp)
418538: 00000000 nop
free(str);
...
如代码所示,出问题的语句在函数httpnPrintf()中,而它传入的参数是由函数cJSON_Print()生成的字符串,cJSON_Print()是开源的cJSON库中的函数,负责将json格式的对象转换为可打印字符串。进一步跟踪发现,某些页面的列表内容为空,导致cJSON_Print()返回的指针str为NULL,导致httpnPrintf()中访问了不合法的内存地址。但奇怪的一点是,旧版产品使用的cJSON库和新版完全一样,空列表的情况在旧版产品上也很常见,并不会引发这种问题。为了寻找根本原因,只能继续跟进cJSON库的源代码,终于在print_array()中发现了疑点。
static char *print_array(cJSON *item, int depth, int fmt)
{
char **entries;
char *out = 0, *ptr, *ret;
int len = 5;
cJSON *child = item->child;
int numentries = 0, i = 0, fail = 0;
/* How many entries in the array? */
while (child)
numentries++, child=child->next;
/* Allocate an array to hold the values for each */
entries = (char**)cJSON_malloc(numentries*sizeof(char*));
if (!entries)
return 0;
memset(entries, 0, numentries*sizeof(char*));
...
}
问题就出在这里,cJSON_malloc函数调用了C库的malloc函数,当页面列表为空时,传入的参数item为空数组,此时numentries的值为0。所以实际上这里调用的是malloc(0)。出问题时函数就是在这之后返回的。旧版产品与新版产品使用的cJSON库一模一样,问题只能出在malloc()函数本身。跟进发现旧版产品使用的工具链与新版不同,难道是工具链中的C库在实现上有区别,导致malloc()函数行为不同?在malloc(0)后打印指针的值,果然发现了区别——旧版工具链malloc(0)后仍然返回一个非空指针,而新版工具链malloc(0)则返回0,即空指针。而函数print_array()是被递归调用的,当它返回空指针,最顶层的打印函数cJSON_Print()自然也会返回空指针。至此,问题得以定位。
同样是malloc函数,在不同的C库中行为也会不同,所以在使用第三方提供的库函数时,不能对其做任何的假设,还需要具体问题具体分析才行。