一、遇到的问题
curlftpfs程序,遇到带有“#”的目录就会出现“double free or corruption (out): 0x00007fe160002b70 ***”,挂载目录就会卸载。
二、分析问题
-
收集core文件。
-
分析core文件,定位到ftpfs_getdir函数的free处(31行)出错。
static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h,
fuse_cache_dirfil_t filler) {
int err = 0;
CURLcode curl_res;
char* dir_path = get_fulldir_path(path);
//将目录名(即url)中的#替换为%23
str_replace(dir_path, "#", "%23");
DEBUG(1, "ftpfs_getdir: %s\n", dir_path);
struct buffer buf;
buf_init(&buf);
pthread_mutex_lock(&ftpfs.lock);
cancel_previous_multi();
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, dir_path);
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
curl_res = curl_easy_perform(ftpfs.connection);
pthread_mutex_unlock(&ftpfs.lock);
if (curl_res != 0) {
DEBUG(1, "%s\n", error_buf);
DEBUG(1, "%d, dir = %s\n", curl_res, dir_path);
err = -EIO;
} else {
buf_null_terminate(&buf);
parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1,
NULL, NULL, NULL, 0, h, filler);
}
free(dir_path);
buf_free(&buf);
return op_return(err, "ftpfs_getdir");
}
static int str_replace(char* str_find,char* str_src, char* str_des)
{
char *ptr=NULL;
char buff[1024];
char buff2[strlen(str_find)+1];
int i = 0;
int num = 0;
if(str_find != NULL)
{
strcpy(buff2, str_find);
}
else
{
DEBUG(1,"str_replace err!\n");
return -1;
}
memset(buff, 0x00, sizeof(buff));
while((ptr = strstr( buff2, str_src)) !=0)
{
if(ptr-buff2 != 0)
memcpy(&buff[i], buff2, ptr - buff2);
memcpy(&buff[i + ptr - buff2], str_des, strlen(str_des));
i += ptr - buff2 + strlen(str_des);
strcpy(buff2, ptr + strlen(str_src));
num ++;
}
strcat(buff,buff2);
str_find = realloc(str_find, strlen(str_find) + num * 3 + 1);
strcpy(str_find,buff);
return 0;
}
刚开始误以为是get_fulldir_path函数内部没有执行malloc申请动态内存,而在ftpfs_getdir中执行了free才做,后来证明此想法是错的,ftpfs_getdir调用了封装有malloc的函数。
最后确认是str_replace函数有问题,str_replace执行了realloc(free后,重新malloc)操作,有时会改变str_find的值,因为调用str_replace时,传递的是str_find的一级指针,str_replace改变了str_find的值但修改无法传递回ftpfs_getdir函数中,导致ftpfs_getdir函数最后free时,释放的是已经释放的地址(realloc已经释放过了)
修改:向str_replace中传递str_find的二级指针,代码如下:
static int str_replace(char** str_origal,char* str_src, char* str_des)
{
if(str_origal == NULL || NULL == str_src || NULL == str_des )
{
return -1;
}
char *ptr=NULL;
char buff[1024];
char *str_find = *str_origal;
char buff2[strlen(str_find)+1];
int i = 0;
int num = 0;
if(str_find != NULL )
{
strcpy(buff2, str_find);
}
else
{
DEBUG(1,"str_replace err!\n");
return -1;
}
memset(buff, 0x00, sizeof(buff));
while((ptr = strstr( buff2, str_src)) !=0)
{
if(ptr-buff2 != 0)
memcpy(&buff[i], buff2, ptr - buff2);
memcpy(&buff[i + ptr - buff2], str_des, strlen(str_des));
i += ptr - buff2 + strlen(str_des);
strcpy(buff2, ptr + strlen(str_src));
num ++;
}
strcat(buff,buff2);
str_find = realloc(str_find, strlen(str_find) + num * 3 + 1);
strcpy(str_find,buff);
*str_origal = str_find;
return 0;
}
三、此类问题原因
-
内存写入越界,最后free的字节数超过malloc的字节数,导致报错
查看案例
- 在调用函数中修改了指针值,但是并未将修改结果返回。 上述一、二所述。
四、定位此类问题的方法
-
排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。
-
检查所有的内存操作函数,检查内存越界的可能。
查找代码里所有申请动态空间的地方,把申请到的空间的地址打印出来;
再查找代码里所有free这些空间的地方,把free的地址打印出来。
然后查看是哪些地址发生了重复free。
如果不存在重复free的现象,就可能是某些地方使用了已free的空间,在死机的位置确认使用了哪些动态空间,然后确认这些动态空间是不是已经被free了。
打印指针值得方法printf(“%p\n”,pointer); //不要取指针的地址&pointer
五、经验教训
-
指针变量要初始化。
memset(p,0,100);memset(p,0,100);这样确保数据之后的字符是’\0’,不会出现读越界 -
指针释放后要赋值为NULL。
因为程序对NULL进行重复释放不会出问题,也不会报错。 -
strncpy()、strncmp()、strncat()、strnicmp()和 strnset() 相应地代替strcmp()、strcat()、stricmp()和 strset()。