平台崩溃之operator new异常(二)-2010-5-13

2010-5-13
今天准备处理的问题(1):查出平台启动之初,本地服务器连不上平台。
处理思路是:通过分析本地服务器的日志,看看为什么连接不上平台。本地服务器都是部署在客户那里,需要请维护部门的同事拿一家客户的日志回来看。
先看看平台日志。
平台日志分析发现2个问题
1)有一个501请求(服务器上线请求)的处理线程产生了异常,该线程的日志为:
[2010-05-13 08:04:23:968](线程18448)正在处理:服务器注册(5:501,cmdserial=614)...
[2010-05-13 08:04:23:968](线程18448)服务器ID为5002(IP:172.20.10.234,Port:9601,MIP:0.0.0.0,MPort:0)的服务器连接到平台。
[2010-05-13 08:04:23:968](线程18448)OnSererLogin() Server ID:5002...
[2010-05-13 08:04:23:968](线程18448)GetServerCert() ServerID:5002...
[2010-05-13 08:04:23:968](线程18448)GetServer() ServerID:5002...
[2010-05-13 08:04:23:968](线程18448)验证机器指纹 ServerID:5002...
[2010-05-13 08:04:23:968](线程18448)产生会话密钥 ServerID:5002...
[2010-05-13 08:04:23:984](线程18448)GenSessionKey()...
[2010-05-13 08:04:25:015](线程18448)插件sap.dll处理"服务器注册"(5:501)产生异常
[2010-05-13 08:04:25:015](线程18448)请求"服务器注册"(501,cmdserial=614)处理完成,返回值:-1,错误码:0,错误描述未定义错误..
最后一条日志为:[2010-05-13 08:04:23:984](线程18448)GenSessionKey()...
正常情况下,下一条日志为:[×××](线程18448)验证机构信息 ServerID:5002...
这两条日志中间的代码片段为:
 DEBUG_LOG(nlogger_,(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"GenSessionKey().../n"));///<最后一条日志
 enum { TOKEN_SIZE = 8};
 unsigned char token[TOKEN_SIZE+1],key[SESSION_KEY_SIZE+1];
 GenSessionKey(token,sizeof(token)/sizeof(token[0])); ///< 生成随机令牌,服务器之间p2p用来被验证
 GenSessionKey(key,sizeof(key)/sizeof(key[0]));/// 产生会话密钥
 /// 用对方公钥加密会话密钥
 unsigned char *outdata = 0;
 unsigned long outlen;
 if (CSecurityWrapper::PublicKeyEncryptData(pkey,key,SESSION_KEY_SIZE,&outdata,outlen)) {///<1.可能产生异常吗?
  delete ans;
  EVP_PKEY_free(pkey);
  if (outdata) OPENSSL_free(outdata);
  RespError(req,out,ERR_ENCRYPT,true);
  return -8;
 }
 ans->msg->AddParam("sk",(char*)outdata,outlen);
 OPENSSL_free(outdata);
 outdata = 0;
 if (CSecurityWrapper::EncryptData(key,token,TOKEN_SIZE,&outdata,outlen)) {///<2.可能产生异常吗?
  delete ans;
  EVP_PKEY_free(pkey);
  if (outdata) OPENSSL_free(outdata);
  RespError(req,out,ERR_ENCRYPT,true);
  return -9;
 }
 ans->msg->AddParam("Token",(char*)outdata,outlen);
 OPENSSL_free(outdata);
 EVP_PKEY_free(pkey);
 //记录服务器上线时间
 time_t svr_online_time;
 time(&svr_online_time);

 server = new SERVERINFO();
 server->svr_id(ulServerID);
 server->handle(in->connid[0]);
 server->svr_name(name);
 server->svr_desc(desc);
 server->ip(ip);
 server->preserved_port(atoi(pport));
 server->nat_type(natt[0]);
 if (mip) server->m_ip(mip);
 if (mport) server->m_port(atoi(mport));
 server->pkg_type(atol(pkg_type));
 server->pkg_version(pkg_version);
 server->clt_pkg_type(atol(c_pkg_type));
 server->clt_version(client_version);
 server->ct_mode(ct_mode);
 server->onlinetime(svr_online_time);
 DEBUG_LOG(nlogger_,(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"验证机构信息 ServerID:%s.../n",serverid));///<这条日志没有输出

 那么,该线程的异常就产生于这个代码片段。
 处理方式:
  做一个测试程序,起动n个线程(我用的是100)来循环执行类似以上代码片段的代码(主要是访问openssl),看看是否会有异常出现?(注意openssl的线程安全回调函数必须加上,否则必然异常)
  经测试,产生了异常,异常代码处为PublicKeyEncryptData
  该函数实现如下:
int CSecurityWrapper::PublicKeyEncryptData(EVP_PKEY *pKey,unsigned char *indata,unsigned long inlen,unsigned char **outdata,unsigned long &outlen)
{
 *outdata = 0;
 int keysize;
 RSA *rsa;
 rsa = EVP_PKEY_get1_RSA(pKey);
 keysize = RSA_size(rsa);
 if (inlen>2*keysize) {
  RSA_free(rsa);
  return -1;
 }
 *outdata = reinterpret_cast<unsigned char*>(OPENSSL_malloc(keysize));
 outlen  = RSA_public_encrypt(inlen, indata, *outdata, rsa, RSA_PKCS1_PADDING);///<该行产生了异常
 if (outlen==-1) {
  OPENSSL_free(*outdata);
 }
 RSA_free(rsa);
 return 0;
}
 修改代码,对RSA_public_encrypt函数加上异常捕捉
 对于该问题,需要进一步解决RSA_public_encrypt的异常产生原因。初步判断是库libeay32.dll的编译选项问题。应用程序用的是Debug Multithreaded DLL(debug),而编译openssl的时候用的是Multithreaded DLL(release).
  
2)所有的501请求(服务器上线请求)的处理线程都阻塞了,处理线程的最后一条日志为:
[2010-05-13 08:22:25:296](线程18520)GetServerCert() ServerID:3976...
线程18520的在输出以上日志后,再没有日志输出。正常情况下,该日志的下条日志为:
 [2010-05-13 ***](线程18448)GetServer() ServerID:3976...
 对应的代码片段如下: 

 DEBUG_LOG(nlogger_,(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"GetServerCert() ServerID:%s.../n",serverid));///<最后一条日志在这里
 CQQ_SERVERID ulServerID = atol(serverid);
 CServerRecordInfo srv_rec;
 if (GetServerRecord(ulServerID,srv_rec)) {///<1.该处可能堵死吗?GetServerRecord从mdb内存数据库中获取服务器的证书,在取数据库连接时有可能堵死.
  RespError(req,out,ERR_CERT_NOFOUND,true);
  return -5;
 }
 X509 *cert = srv_rec.x509_;
 EVP_PKEY *pkey = X509_get_pubkey(cert);///<2.该处可能堵死吗?
 if (pkey==0) {
  RespError(req,out,ERR_GET_PUBLICKEY,true);
  return -6;
 }
 int rv = 0;
 rv = CSecurityWrapper::VerifySign(pkey,(unsigned char*)serverid,strlen(serverid),(unsigned char*)fingerprint,fp_len);///<3.该处可能堵死吗?
 if (rv) { ///< 验证失败
  EVP_PKEY_free(pkey);
  RespError(req,out,ERR_VERIFY_SERVER,true);
  return -7;
 }

 DEBUG_LOG(nlogger_,(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"GetServer() ServerID:%s.../n",serverid));///<这条日志没有输出

 那么,线程都死在这两条日志的中间处。
 处理方法:在可能堵死的地方(1,2,3)处加上输出日志。
 经查,这个问题是第一个问题的连带问题。第一个问题产生异常后,在异常处函数退出,栈上对象没有得到析构,导致数据库的互斥锁和openssl的互斥锁不能得到正确释放。测试代码如下
class TestDetruture{
public:
 TestDetruture(){
  buf = new char[100];
 }
 virtual ~TestDetruture(){
  printf("~TestDetruture/n");///<异常产生后,该行代码执行不到
  delete []buf;
 }
 char* buf;
};
int fun(){
 TestDetruture td;
 //throw int();///<能够析构td
 char* p = NULL;
 strcpy(p,"dsfsdf");///<不能析构td
 printf("test /n");
 return 0;
}
int main(int argc, char* argv[])
{
 printf("Hello World!/n");
 try{
  fun();
 }catch(...){
  printf("fun exception./n");
 }
 while(1){
  int a;
  scanf("%d",&a);
  if(a==0)
   break;
 }
 return 0;
}

 这个测试程序表明,如果用throw抛出的异常,栈上对象能够被析构;而当程序错误造成的非预期异常产生后,栈上的对象调不到析构函数。所以,导致第二个问题出现。
 参考文献:http://www.vckbase.com/document/viewdoc/?id=937

今天准备处理的问题(2):
 平台处理382请求(获取客户化软件包版本编号)产生first-chance exception。
 异常处:ado执行select查询语句处。数据库为mysql 5.0.4,mysql odbc驱动为3.51.19
 处理思路:准备在测试环境下重现此问题。
 在测试环境下确实能重现此问题。382协议每次必然报出first-chance exception出来。
 开始怀疑是sql语句过长,准备用如下代码进行测试
 const char buf[] = "select f001n_0022 from tb_0022 ";
 string strSQL = buf;
 for(int i=0;i<100;++i){
  CRecordset* p = pdbor->Query(adCmdText,strSQL.c_str());
  pdbor->ReleaseRecordset(p);
  strSQL = LogMsg("%s union all %s",strSQL.c_str(),buf);
 }
 结果发现for循环执行第二次的时候就重现了。此时,sql语句肯定没有超长。排除sql语句超长问题。
 开始怀疑union all的语法问题。这条sql在mysql的客户端上跑没问题。奇怪?
 最后,将union all的前后子句加上扩号,问题得到解决。代码如下所示:
 const char buf[] = "(select f001n_0022 from tb_0022) ";
 string strSQL = buf;
 for(int i=0;i<100;++i){
  CRecordset* p = pdbor->Query(adCmdText,strSQL.c_str());
  pdbor->ReleaseRecordset(p);
  strSQL = LogMsg("%s union all %s",strSQL.c_str(),buf);
 }
 
 今天晚上10:30左右编译程序,重启了平台,待进一步观察。明天确定openssl Debug Multithreaded DLL库的测试。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值