VS2005 c++build的release程序,发现memcpy的地方错误(看不出越界,求助)

fluke 2009-09-06 04:05:08
我的代码是从mp4文件中获取一些信息,然后返回。

mp4的文件结构类似一棵树,每个节点都有一个大端long表示该节点大小,跟着一个ASCII的4字节(FOURCC)表示节点名字,然后才是节点名字。

我要找的路径是:root->moov->mvhd,然后在这个节点获取指定字符(其实就是mp4的duration),一旦这个路径找不到或者别的出错,函数就返回-1。

--------------

上面知识其实和这个问题关系不是非常大,我把代码都贴出来,错误的地方也指出来,希望有人能指出错误。

主函数:传入参数是mp4文件(已经载入内存了,是const char的buf),mp4文件长度。两个参数都是const的。
看函数定义:

/**
* @brief 从内存进行文件duration判定
* TODO: 暂时还是模拟文件读取的方式,是copy的
*/
int GetMP4DurationInMem(const unsigned char *apBuf, const int anBufLen)
{
if(!anBufLen) {
fprintf(stderr, "filesize is zero\n");
return -1;
}

int file_ptr = 0;
unsigned long filesize = anBufLen;
// init the atom size stack
top = 0;
size_stack[top] = filesize;

unsigned long duration = 0;

char buf[5];
unsigned long offset = 0;
int deep = 0; // TODO: 这里小心深度太深。不过这个应用里面是安全的。
while(1) {
if(++deep > 100) {
//printf("reach max depth, abort\n");
break;
}
if(offset >= size_stack[top]) {
if(--top < 0) {
break;
}
}

#ifdef DEBUG_MEM_ALLOC
if(file_ptr != offset) {
FILE *fp = fopen("c:\\cm_mp4_debug.txt", "a+");
if(fp) {
int filesize = 0;
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
rewind(fp);

if(filesize < 1024*1024*10) {
fprintf(fp, "file_ptr %d, offset %d\r\n");
fprintf(fp, "start\r\n");
fwrite(apBuf, anBufLen, 1, fp);
fprintf(fp, "end\r\n");
} else {
fprintf(fp, "the log file is too large, not write stream data\r\n");
}

fclose(fp);
}
break; // 错误了
}
#endif

//printf("\noffset: %d\n", offset);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4); // ========== (1)
file_ptr += 4;
unsigned long atom_size = GetLong((unsigned char*)buf); buf[4] = 0;
//printf("%X %X %X %X \n", buf[0], buf[1], buf[2], buf[3]);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4;
//printf("Atom size: %d, name: %c%c%c%c\n", atom_size, buf[0], buf[1], buf[2], buf[3]);

if(!strncmp("moov", buf, 4)) {
//printf(">> Find moov\n");

GoIntoAtomInMem(atom_size, &offset);
continue;
}

// get mvhd, then we go back to the moov atom
if(!strncmp("mvhd", buf, 4)) {
//printf(">> Find mvhd\n");

// get mvhd version
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4;
if( 0 != GetLong((unsigned char *)buf) ) {
fprintf(stderr, "We only accept movie that mvhd version == 0, abort\n");
break;
}
file_ptr += 8; // skip the creation an monification time
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4; // get timescale
unsigned long timescale = GetLong((unsigned char *)buf);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4; // get duration
duration = GetLong((unsigned char *)buf);
duration /= timescale;


// we break here because this time we only need the duration from mvhd, but not track->mdhd
return duration * 1000; // in millisecond

// seek back to the beginning of this atom in order ot let the SkipAtom work correctly
file_ptr -= 20;

file_ptr += SkipAtomInMem(apBuf, atom_size, &offset);
continue;
}

// default skip
file_ptr += SkipAtomInMem(apBuf, atom_size, &offset);
}

return -1;
}

其中,错误发生在上面的:
memcpy(buf, apBuf+file_ptr, 4); // ========== (1)
这行,我在维护的file_ptr是mp4文件(内存映射)的pos,这个pos已经有判断,不能超出文件大小,如果超出的话,就退出了,相当于遇到文件的EOF。

这里遇到的错误是:

弹出应用程序: Test.exe - 应用程序错误: "0x00414343" 指令引用的 "0x01b23008" 内存。该内存不能为 "read"。

这个指令对应的反汇编上下文是:

//printf("\noffset: %d\n", offset);
if(file_ptr >= anBufLen-4) return -1;
00414338 add eax,0FFFFFFFCh
0041433B cmp edi,eax
0041433D jge GetMP4DurationInMem+25Ah (41450Ah)
memcpy(buf, apBuf+file_ptr, 4);
00414343 mov edx,dword ptr [ebx+edi]
00414346 mov dword ptr [esp+10h],edx

系统是Windows 2003, 2gRAM.

程序运行实例,30个左右。

上面主程序用到的两个函数如下:

// 内存进去atom
void GoIntoAtomInMem(unsigned long atom_size, unsigned long *offset)
{
*offset += 8; // len( size + 4cc )
size_stack[++top] = atom_size;
}

// 内存跳过当前atom
int SkipAtomInMem(const unsigned char *apBuf, unsigned long atom_size, unsigned long *offset)
{
*offset += atom_size;
return atom_size - 8; // 返回file_ptr的增量,模拟fseek
}

上面这两个函数也参加了维护文件指针 file_ptr。

对于上面三个函数,都是对内存中的mp4内容来进行解析,也有一个版本是直接针对文件的,也贴出来做参考:


unsigned long size_stack[100];
int top;

unsigned long GetLong(const unsigned char *buf)
{
int ret = 0;
for(int i=0;i<4;++i) {
ret *= 256;
ret += buf[i];
}
return ret;
}


void GoIntoAtom(FILE *fp, unsigned long atom_size, unsigned long *offset)
{
// no seek , already in
// unused fp
*offset += 8; // len( size + 4cc )
size_stack[++top] = atom_size;
}

void SkipAtom(FILE *fp, unsigned long atom_size, unsigned long *offset)
{
fseek(fp, atom_size - 8, SEEK_CUR);
*offset += atom_size;
}

/**
* @brief 根据fp来获取mp4文件的duration
*/
// TODO: considering get duration from track->mdhd
int GetMP4Duration(FILE *fp)
{
if(!fp) {
fprintf(stderr, "the file fp is invalid\n");
return -1;
}

unsigned long filesize = 0;
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
//printf("filesize: %d\n", filesize);
rewind(fp);

// init the atom size stack
top = 0;
size_stack[top] = filesize;

unsigned long duration = 0;

char buf[5];
unsigned long offset = 0;
int deep = 0; // TODO: 这里小心深度太深。不过这个应用里面是安全的。
while(1) {
if(++deep > 100) {
//printf("reach max depth, abort\n");
break;
}
if(offset >= size_stack[top]) {
if(--top < 0) {
break;
}
}
//printf("\noffset: %d\n", offset);
fread(buf, 1, 4, fp);
unsigned long atom_size = GetLong((unsigned char*)buf); buf[4] = 0;
//printf("%X %X %X %X \n", buf[0], buf[1], buf[2], buf[3]);
fread(buf, 1, 4, fp);
//printf("Atom size: %d, name: %c%c%c%c\n", atom_size, buf[0], buf[1], buf[2], buf[3]);

if(!strncmp("moov", buf, 4)) {
//printf(">> Find moov\n");

GoIntoAtom(fp, atom_size, &offset);
continue;
}

// get mvhd, then we go back to the moov atom
if(!strncmp("mvhd", buf, 4)) {
//printf(">> Find mvhd\n");

// get mvhd version
fread(buf, 1, 4, fp);
if( 0 != GetLong((unsigned char *)buf) ) {
fprintf(stderr, "We only accept movie that mvhd version == 0, abort\n");
break;
}
fseek(fp, 8, SEEK_CUR); // skip the creation an monification time
fread(buf, 1, 4, fp); // get timescale
unsigned long timescale = GetLong((unsigned char *)buf);
fread(buf, 1, 4, fp); // get duration
duration = GetLong((unsigned char *)buf);
duration /= timescale;


// we break here because this time we only need the duration from mvhd, but not track->mdhd
rewind(fp);
return duration * 1000; // in millisecond

// seek back to the beginning of this atom in order ot let the SkipAtom work correctly
fseek(fp, -20, SEEK_CUR);

SkipAtom(fp, atom_size, &offset);
continue;
}

// default skip
SkipAtom(fp, atom_size, &offset);
}

rewind(fp);
return -1;
}

谢谢!
...全文
340 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
zeerd 2009-09-07
  • 打赏
  • 举报
回复
貌似没有对apBuff进行有效性判断,这个能确保没有问题吗?
myhder 2009-09-07
  • 打赏
  • 举报
回复
头晕
没看完
whg01 2009-09-07
  • 打赏
  • 举报
回复
fopen一定能打开文件吗?
程序运行实例30个左右什么意思?并行?
命令行的话,多输出一些信息。
窗口应用的话,用注册表或应用程序的消息。写文件是有缓存的,所以写log文件,很有可能最后一些内容不会被写入到文件中。
zhongchengli 2009-09-07
  • 打赏
  • 举报
回复
首先的再现问题,这种猜测可能会走弯路!
thy38 2009-09-07
  • 打赏
  • 举报
回复
不调试根本没法看,连个测试数据都没有
zgjxwl 2009-09-07
  • 打赏
  • 举报
回复
帮顶下。
fluke 2009-09-07
  • 打赏
  • 举报
回复
apBuff是读取文件的缓冲,保证有效,如果无效,会在函数的另外一个参数 anBufLen上面体现出来,是一个负数。

fopen不保证能打开文件,如果不能打开的话,anBufLen就是负数。

所以应该是可靠的,估计问题不出在这里。
Daniel_31 2009-09-06
  • 打赏
  • 举报
回复
............
fluke 2009-09-06
  • 打赏
  • 举报
回复
生产环境要调试几乎是不可能。我很难装一个开发环境过去。

我本机暂时还没有重现到,只是根据地址来查找代码的。


int GetMP4DurationInMem(const unsigned char *apBuf, const int anBufLen)
这里的apBuf是fread得到的文件内容以及大小。

读取文件的代码,如下:

int filesize;
// 直接获取文件大小
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
rewind(fp);

if(filesize < 1024 * 1024) { // 文件要小于1M,已经很宽松了
psMediaFile->filebuf = (unsigned char*)ucMalloc(filesize);
psMediaFile->filesize = filesize;
}

bool bPreLoadFailed = false;
int bytesRead;
if(psMediaFile->filebuf) {
unsigned char *ptr = psMediaFile->filebuf;
int total_bytesRead = 0;
int try_count = 0;
while(true) {
bytesRead = fread(ptr, 1, filesize-total_bytesRead, fp ) ; // TODO: 检查是否成功
total_bytesRead += bytesRead;
szLog.Format(L"Pre-load bytes: %d", bytesRead);
AppendLog(szLog, 10);
if(total_bytesRead >= filesize) {
szLog.Format(L"Pre-load succ");
AppendLog(szLog, 10);
break;
}
if(++try_count > 100) {
szLog.Format(L"Pre-load failed, total bytes %d", total_bytesRead);
AppendLog(szLog, 10);
bPreLoadFailed = true;
break;
}
}

}
fclose(fp);
if(bPreLoadFailed) {
psMediaFile->filesize = -2; // 表示错误
}

上面的代码可以保证,得到的buf大小一定是有效可靠的,如果读不到filesize这么多内容的话,会把传入GetMp4Duration函数的长度设置成无效的值。

上面说完了保证buf和长度有效,现在说保证memcpy的参数有效:

if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);

可见,我保证了apBuf是const, file_ptr也不能超越anBufLen-4,所以这里复制4个字节应该是有效的。

这是我觉得诡异的地方。

我知道代码确实有点多,不过我自己review过几次,实在找不到问题。所以拿上来问下。

另外,我用内存缓冲作为函数的参数而不是文件名,是因为发现多线程同时对同一个文件打开(不同的句柄),貌似有一定的几率会出现问题,不知道是不是和系统维护同一个文件的不同描述符的时候有问题,不过这是另外一个话题了。

感谢大家能抽时间看这个问题。
arong1234 2009-09-06
  • 打赏
  • 举报
回复
这么多代码要人家一眼看出错误几乎是不可能的,这是为什么需要“调试”的原因,很多问题不是看出来的,而是调试出来的

简单的说,你能不能包装memcpy时三个参数的合法性,这才是问题的关键
mstlq 2009-09-06
  • 打赏
  • 举报
回复
很诡异哦……
可以将执行memcpy(buf, apBuf+file_ptr, 4);
之前的buf地址,apBuf地址,file_ptr值和anBufLen都输出来看看嘛^_^?
fluke 2009-09-06
  • 打赏
  • 举报
回复
下面是有ubb的版本,方便查看代码:

我的代码是从mp4文件中获取一些信息,然后返回。

mp4的文件结构类似一棵树,每个节点都有一个大端long表示该节点大小,跟着一个ASCII的4字节(FOURCC)表示节点名字,然后才是节点名字。

我要找的路径是:root->moov->mvhd,然后在这个节点获取指定字符(其实就是mp4的duration),一旦这个路径找不到或者别的出错,函数就返回-1。

--------------

上面知识其实和这个问题关系不是非常大,我把代码都贴出来,错误的地方也指出来,希望有人能指出错误。

主函数:传入参数是mp4文件(已经载入内存了,是const char的buf),mp4文件长度。两个参数都是const的。
看函数定义:


/**
* @brief 从内存进行文件duration判定
* TODO: 暂时还是模拟文件读取的方式,是copy的
*/
int GetMP4DurationInMem(const unsigned char *apBuf, const int anBufLen)
{
if(!anBufLen) {
fprintf(stderr, "filesize is zero\n");
return -1;
}

int file_ptr = 0;
unsigned long filesize = anBufLen;
// init the atom size stack
top = 0;
size_stack[top] = filesize;

unsigned long duration = 0;

char buf[5];
unsigned long offset = 0;
int deep = 0; // TODO: 这里小心深度太深。不过这个应用里面是安全的。
while(1) {
if(++deep > 100) {
//printf("reach max depth, abort\n");
break;
}
if(offset >= size_stack[top]) {
if(--top < 0) {
break;
}
}

#ifdef DEBUG_MEM_ALLOC
if(file_ptr != offset) {
FILE *fp = fopen("c:\\cm_mp4_debug.txt", "a+");
if(fp) {
int filesize = 0;
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
rewind(fp);

if(filesize < 1024*1024*10) {
fprintf(fp, "file_ptr %d, offset %d\r\n");
fprintf(fp, "start\r\n");
fwrite(apBuf, anBufLen, 1, fp);
fprintf(fp, "end\r\n");
} else {
fprintf(fp, "the log file is too large, not write stream data\r\n");
}

fclose(fp);
}
break; // 错误了
}
#endif

//printf("\noffset: %d\n", offset);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4); // ========== (1)
file_ptr += 4;
unsigned long atom_size = GetLong((unsigned char*)buf); buf[4] = 0;
//printf("%X %X %X %X \n", buf[0], buf[1], buf[2], buf[3]);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4;
//printf("Atom size: %d, name: %c%c%c%c\n", atom_size, buf[0], buf[1], buf[2], buf[3]);

if(!strncmp("moov", buf, 4)) {
//printf(">> Find moov\n");

GoIntoAtomInMem(atom_size, &offset);
continue;
}

// get mvhd, then we go back to the moov atom
if(!strncmp("mvhd", buf, 4)) {
//printf(">> Find mvhd\n");

// get mvhd version
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4;
if( 0 != GetLong((unsigned char *)buf) ) {
fprintf(stderr, "We only accept movie that mvhd version == 0, abort\n");
break;
}
file_ptr += 8; // skip the creation an monification time
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4; // get timescale
unsigned long timescale = GetLong((unsigned char *)buf);
if(file_ptr >= anBufLen-4) return -1;
memcpy(buf, apBuf+file_ptr, 4);
file_ptr += 4; // get duration
duration = GetLong((unsigned char *)buf);
duration /= timescale;


// we break here because this time we only need the duration from mvhd, but not track->mdhd
return duration * 1000; // in millisecond

// seek back to the beginning of this atom in order ot let the SkipAtom work correctly
file_ptr -= 20;

file_ptr += SkipAtomInMem(apBuf, atom_size, &offset);
continue;
}

// default skip
file_ptr += SkipAtomInMem(apBuf, atom_size, &offset);
}

return -1;
}


其中,错误发生在上面的:
memcpy(buf, apBuf+file_ptr, 4); // ========== (1)
这行,我在维护的file_ptr是mp4文件(内存映射)的pos,这个pos已经有判断,不能超出文件大小,如果超出的话,就退出了,相当于遇到文件的EOF。

这里遇到的错误是:

弹出应用程序: Test.exe - 应用程序错误: "0x00414343" 指令引用的 "0x01b23008" 内存。该内存不能为 "read"。

这个指令对应的反汇编上下文是:

//printf("\noffset: %d\n", offset);
if(file_ptr >= anBufLen-4) return -1;
00414338 add eax,0FFFFFFFCh
0041433B cmp edi,eax
0041433D jge GetMP4DurationInMem+25Ah (41450Ah)
memcpy(buf, apBuf+file_ptr, 4);
00414343 mov edx,dword ptr [ebx+edi]
00414346 mov dword ptr [esp+10h],edx


系统是Windows 2003, 2gRAM.

程序运行实例,30个左右。

上面主程序用到的两个函数如下:


// 内存进去atom
void GoIntoAtomInMem(unsigned long atom_size, unsigned long *offset)
{
*offset += 8; // len( size + 4cc )
size_stack[++top] = atom_size;
}

// 内存跳过当前atom
int SkipAtomInMem(const unsigned char *apBuf, unsigned long atom_size, unsigned long *offset)
{
*offset += atom_size;
return atom_size - 8; // 返回file_ptr的增量,模拟fseek
}


上面这两个函数也参加了维护文件指针 file_ptr。

对于上面三个函数,都是对内存中的mp4内容来进行解析,也有一个版本是直接针对文件的,也贴出来做参考:


unsigned long size_stack[100];
int top;

unsigned long GetLong(const unsigned char *buf)
{
int ret = 0;
for(int i=0;i<4;++i) {
ret *= 256;
ret += buf[i];
}
return ret;
}


void GoIntoAtom(FILE *fp, unsigned long atom_size, unsigned long *offset)
{
// no seek , already in
// unused fp
*offset += 8; // len( size + 4cc )
size_stack[++top] = atom_size;
}

void SkipAtom(FILE *fp, unsigned long atom_size, unsigned long *offset)
{
fseek(fp, atom_size - 8, SEEK_CUR);
*offset += atom_size;
}

/**
* @brief 根据fp来获取mp4文件的duration
*/
// TODO: considering get duration from track->mdhd
int GetMP4Duration(FILE *fp)
{
if(!fp) {
fprintf(stderr, "the file fp is invalid\n");
return -1;
}

unsigned long filesize = 0;
fseek(fp, 0, SEEK_END);
filesize = ftell(fp);
//printf("filesize: %d\n", filesize);
rewind(fp);

// init the atom size stack
top = 0;
size_stack[top] = filesize;

unsigned long duration = 0;

char buf[5];
unsigned long offset = 0;
int deep = 0; // TODO: 这里小心深度太深。不过这个应用里面是安全的。
while(1) {
if(++deep > 100) {
//printf("reach max depth, abort\n");
break;
}
if(offset >= size_stack[top]) {
if(--top < 0) {
break;
}
}
//printf("\noffset: %d\n", offset);
fread(buf, 1, 4, fp);
unsigned long atom_size = GetLong((unsigned char*)buf); buf[4] = 0;
//printf("%X %X %X %X \n", buf[0], buf[1], buf[2], buf[3]);
fread(buf, 1, 4, fp);
//printf("Atom size: %d, name: %c%c%c%c\n", atom_size, buf[0], buf[1], buf[2], buf[3]);

if(!strncmp("moov", buf, 4)) {
//printf(">> Find moov\n");

GoIntoAtom(fp, atom_size, &offset);
continue;
}

// get mvhd, then we go back to the moov atom
if(!strncmp("mvhd", buf, 4)) {
//printf(">> Find mvhd\n");

// get mvhd version
fread(buf, 1, 4, fp);
if( 0 != GetLong((unsigned char *)buf) ) {
fprintf(stderr, "We only accept movie that mvhd version == 0, abort\n");
break;
}
fseek(fp, 8, SEEK_CUR); // skip the creation an monification time
fread(buf, 1, 4, fp); // get timescale
unsigned long timescale = GetLong((unsigned char *)buf);
fread(buf, 1, 4, fp); // get duration
duration = GetLong((unsigned char *)buf);
duration /= timescale;


// we break here because this time we only need the duration from mvhd, but not track->mdhd
rewind(fp);
return duration * 1000; // in millisecond

// seek back to the beginning of this atom in order ot let the SkipAtom work correctly
fseek(fp, -20, SEEK_CUR);

SkipAtom(fp, atom_size, &offset);
continue;
}

// default skip
SkipAtom(fp, atom_size, &offset);
}

rewind(fp);
return -1;
}


谢谢!

64,282

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧