系列文章目录
车载网络测试实操源码_使用CAPL脚本解析hex、S19、vbf文件
车载网络测试实操源码_使用CAPL脚本对CAN报文的Counter和CRC进行实时监控
车载网络测试实操源码_使用CAPL脚本模拟发送符合协议要求的Counter和CRC的CAN报文
车载网络测试实操源码_使用CAPL脚本实现安全访问解锁
车载网络测试实操源码_使用CAPL脚本进行DTC自动化测试
车载网络测试实操源码_使用CAPL脚本进行UDS刷写及其自动化测试
车载网络测试实操源码_使用CAPL脚本进行UDS协议测试
粉丝问题解答系列文章… …
其他持续更新中… …
前言
VBF(Versatile Binary Format)文件是一种二进制文件格式,被沃尔沃、福特、马自达、吉利等汽车制造商广泛采用。这种格式无法使用我们常规的烧录器进行刷写,通常需要进行解析转化。如果各位需要自己编写软件或脚本来刷写VBF文件,或者需要对刷写功能进行测试时,那么本篇文章就会对你非常有用。今天我们就来讲一下如何使用CAPL脚本对VBF文件进行数据解析。
一、VBF文件介绍
VBF文件包含三部分:VBF版本段、文件头段、数据段。前面的版本段和文件头段是ASCII码,包含版本、地址、校验码等信息;后面的数据段是二进制码,是原始的烧录二进制文件。由于ECU固件通常很大,因此VBF文件通常分为多个数据块,以便在刷写期间逐步传输和写入ECU。具体格式如下:
二、VBF文件解析
以下是一个实际的VBF文件示例,分别是SBL(Secondary Bootloader)和APP的VBF文件,大家可以对应上面的格式进行理解:
根据前面的讲解我们已经知道,上面示例中header{}部分就是文件头段,后面一堆看起来乱码的数据其实就是程序的二进制数据内容,我们实际主要做的就是把header中的相关信息(比如签名值)和数据段中的二进制数据解析出来,用于程序烧写和验签。
话不多说,上代码(以解析上图格式的VBF文件为示例)
/*@!Encoding:936*/
includes
{
}
variables
{
/*sbl和app的vbf文件定义*/
struct VbfData
{
byte Block_1_Address[4];
byte Block_1_Length[4];
byte Block_1_data[0x100000];
byte Block_1_check[2];
byte Block_2_Address[4];
byte Block_2_Length[4];
byte Block_2_data[0x100000];
byte Block_2_check[2];
byte verification_block_start[4];
byte verification_block_length[4];
byte verification_block_data[0x100];
byte verification_block_check[2];
byte sw_signature_dev[0x500];
};
struct VbfData SblVbfData;
struct VbfData AppVbfData;
/* 指定待解析的文件 */
char SblVbf_PathFile[200] = "E:\\TEST\\SBL.vbf";
char AppVbf_PathFile[200] = "E:\\TEST\\APP.vbf";
}
/*将char字符转换为byte*/
byte char2byte(char ch)
{
byte val = 0;
if ( ch >= '0' && ch <= '9')
{
val = ch - '0';
}
if ( ch >= 'a' && ch <= 'f')
{
val = (ch - 'a') + 10;
}
if ( ch >= 'A' && ch <= 'F')
{
val = (ch - 'A') + 10;
}
return val;
}
/*解析VBF文件*/
void vbfFile_Parse(byte File_Select,struct VbfData File_obj)
{
char UpdateFileAdress[200];
char VbfFileHeaderBuffer[0x1000];
char VbfFileHeaderBuffer_tmp[10];
byte VbfFileBinaryBuffer[0x100000];
byte VbfFileBinaryBuffer_tmp[10];
dword UpdateFileHandle;
dword buf_idx;
byte brace_count;
long i;
char strcmp[20] = "sw_signature_dev = ";
byte str_cut;
dword str_cut_start,str_cut_end;
struct
{
dword data;
}Block_1_Length,Block_2_Length,Block_3_Length;
dword Block_1_offset,Block_2_offset;
str_cut = 0;
brace_count = 0;
buf_idx = 0;
/*获取文件路径*/
if(File_Select == 1)//SBL
{
memcpy(UpdateFileAdress,SblVbf_PathFile,elcount(SblVbf_PathFile));
}
else if(File_Select == 2)//APP
{
memcpy(UpdateFileAdress,AppVbf_PathFile,elcount(AppVbf_PathFile));
}
/*以二进制模式打开文件*/
UpdateFileHandle = OpenFileRead(UpdateFileAdress,1);
if(UpdateFileHandle != 0)
{
/*读取Header字符数据*/
while( fileGetString(VbfFileHeaderBuffer_tmp,2,UpdateFileHandle)!=0 )
{
VbfFileHeaderBuffer[buf_idx++] = VbfFileHeaderBuffer_tmp[0];
if(VbfFileHeaderBuffer_tmp[0] == '}'){brace_count++;}//以'}'划分出文件头段和二进制数据段
if((File_Select == 1 && brace_count == 1) || ( File_Select == 2 && brace_count == 5 ))//SBL的Header有1个'}',APP的Header有5个'}'
{
break;
}
}
write ("Header数据总字节数:%x",buf_idx);
for(i=0;i<buf_idx;i++)
{
if(0 == strncmp_off(VbfFileHeaderBuffer,i,strcmp,0,strlen(strcmp)))
{
str_cut = 1;
str_cut_start = i+strlen(strcmp)+2;//当前位置+"sw_signature_dev = 0x"的偏移
}
if(str_cut == 1 && VbfFileHeaderBuffer[i] == ';')
{
str_cut_end = i;
break;
}
}
buf_idx = 0;
for(i = str_cut_start; i < str_cut_end; i += 2)
{
File_obj.sw_signature_dev[buf_idx++]=char2byte(VbfFileHeaderBuffer[i])*0x10 + char2byte(VbfFileHeaderBuffer[i+1]);
}
/*读取下载的二进制数据*/
buf_idx = 0;
while(fileGetBinaryBlock(VbfFileBinaryBuffer_tmp,1,UpdateFileHandle)!=0)
{
VbfFileBinaryBuffer[buf_idx++] = VbfFileBinaryBuffer_tmp[0];
}
write ("二进制数据总字节数:%x",buf_idx);
fileClose (UpdateFileHandle);
}
else
{
write ("Failed to read file.");
}
/*解析Block1*/
memcpy_off(File_obj.Block_1_Address,0,VbfFileBinaryBuffer,0,4);
memcpy_off(File_obj.Block_1_Length,0,VbfFileBinaryBuffer,0+4,4);
memcpy(Block_1_Length,File_obj.Block_1_Length);
Block_1_Length.data = swapDWord(Block_1_Length.data);
memcpy_off(File_obj.Block_1_data,0,VbfFileBinaryBuffer,4+4,Block_1_Length.data);
memcpy_off(File_obj.Block_1_check,0,VbfFileBinaryBuffer,4+4+Block_1_Length.data,2);
Block_1_offset = 4+4+Block_1_Length.data+2;
/*解析Block2*/
if(File_Select == 2) //APP才有Block2
{
memcpy_off(File_obj.Block_2_Address,0,VbfFileBinaryBuffer,Block_1_offset,4);
memcpy_off(File_obj.Block_2_Length,0,VbfFileBinaryBuffer,Block_1_offset+4,4);
memcpy(Block_2_Length,File_obj.Block_2_Length);
Block_2_Length.data = swapDWord(Block_2_Length.data);
memcpy_off(File_obj.Block_2_data,0,VbfFileBinaryBuffer,Block_1_offset+4+4,Block_2_Length.data);
memcpy_off(File_obj.Block_2_check,0,VbfFileBinaryBuffer,Block_1_offset+4+4+Block_2_Length.data,2);
Block_2_offset = Block_1_offset+4+4+Block_2_Length.data+2;
}
else if(File_Select == 1)
{
Block_2_offset = Block_1_offset;
}
/*解析verification_block*/
memcpy_off(File_obj.verification_block_start,0,VbfFileBinaryBuffer,Block_2_offset,4);
memcpy_off(File_obj.verification_block_length,0,VbfFileBinaryBuffer,Block_2_offset+4,4);
memcpy(Block_3_Length,File_obj.verification_block_length);
Block_3_Length.data = swapDWord(Block_3_Length.data);
memcpy_off(File_obj.verification_block_data,0,VbfFileBinaryBuffer,Block_2_offset+4+4,Block_3_Length.data);
memcpy_off(File_obj.verification_block_check,0,VbfFileBinaryBuffer,Block_2_offset+4+4+Block_3_Length.data,2);
}
/*解析函数调用示例*/
void VbfParseMain()
{
vbfFile_Parse(1,SblVbfData);
vbfFile_Parse(2,AppVbfData);
}
解析完的SBL和App程序数据就存在SblVbfData和AppVbfData中,包括文件头段中的签名值和数据段中的多块二进制数据。
以上代码是根据上面的示例VBF文件进行编写的,如果你的VBF文件中数据块个数不一样,需要调整一下脚本中解析的数据块个数。
总结
本文简单介绍了VBF文件的格式,以及如何使用CAPL脚本对VBF文件中的数据进行解析,希望对大家有所帮助。本文只讲解了如何解析出VBF文件中的数据,后续文章会讲解如何将解析出来的数据通过CAN进行刷写下载。