debug 链接到标题

缺陷修复记录 链接到标题

调试的前期准备 链接到标题

修改环境变量,将 startfile 脚本中设置环境变量的部分复制到 .bashrc 中去。

对于单个程序,可以进入 FIP_RISK/02Backend/target/${prog_name} 目录下,通过执行以下命令编译生成可执行文件,以 rmskernel 为例。

amake rsmkernel.prj
make clean
make pump
make -j4

调试时,在 make pump 这一步之后,修改 make file,将 APPEND_CPPFLAGSO3 修改为 O0,再进行编译。

调试打好断点运行时,需要输入 r 1 而不是 r

强平试算结果异常问题 链接到标题

问题表现 链接到标题

在风控管理端的单客户强平功能中,勾选多个交易项然后点击 强平单生成,对于生成后的强平单,分别对单项交易点击 强平试算,只有序号为 1 的项的试算结果是正确的,其他项的结果的释放保证金为 0。

事实上,后期才确定结果的正确性与交易的左侧序号有关,我之前还以为是特定的交易结果不对。

Debug 过程 链接到标题

CKernelRiskReactor::HandleMessage 处打上断点,然后执行 r 1,然后再删除该断点,再在 CRiskServiceImpl::PreReqGenForceCloseOrder 处打上断点,这样处理的好处是可以直接跳过 Redo 的过程(Redo 也会调用 PreReqForceCloseOrder)。

以投资者 801000851 为例,首先,对于 强平单生成按钮,点击之后会执行 x 次 PreReqGenForceCloseOrder,将待处理的交易都添加到一个集合中,然后执行一次 ReqGenForceCloseOrder,处理集合中的这些交易,生成结果(即“推荐强平单”)。

x 为勾选的要生成强平单的交易数量。

在点击 强平试算 按钮时,也会执行 x 次 PreReqForceCloseCalc,将待试算的单子都添加到一个集合中去,然后执行一次 ReqGenForceCloseOrder,对这些单子调用试算核心进行试算,得出试算结果。

我直接勾选了前三个交易以生成“强平推荐单”,其中第二个单子为郑商所的合约,而在计算要释放的保证金的步骤是这样的,首先计算各个投资者在交易所的初始保证金之和(后简称交易所保证金),即 moneyField.InitMargin,对于同一个投资者的不同强平单而言,该值都是一样的。

生成强平单时,我们选择的算法为“按照预结算资金”处理,故 pReqField->Algorithm 不为 RmsFCA_ByRealTimeFunds,具体算法对应的枚举值,可以在 UFEntity.xmlUFDataType.xml 中查找,然后在第二次执行 m_pForceCloseBase->SettleCalc 时,会更新求得 moneyField.Margin,这是通过伪造成交记录后,计算各个投资者在各个交易所的保证金之和得到的,具体内容可以查看 ForceCloseByPrice.cpp 中的 SettleCalc 函数。

SettleCalc 函数中,可以看到会遍历 m_ExchangeVec 进行处理,即每个交易所都处理一次,考虑到我用来测试的第二个单子是郑商所的,那么我只要关注投资者在郑商所的保证金变化即可,这里重点关注 GetResult,将会输出交易所 ID 与 ExTradeMargin 的日志的输出级别从 LOG_EVENT 修改为 LOG_INFO,这样相关日志就会写入到 rms_run/rmskernel1/bin/Syslog.log 中,查看日志,可以发现在执行过 StartSettle 之后,郑商所的保证金仍为 89852,根据益伟哥的提示,本次 bug 大概率出现在 PrepareBasicData 或者清算核心中,这里先检查比较方便检查的 PrepareBasicData

PrepareBaicData 中进行单步调试,对于其中调用的函数,也再进入到其函数内进行单步调试,故进入 GetSettlePosiInfo 中进行单步调试,该函数大概是用于获取结算后的持仓信息的,单步调试时发现一个明显的问题,m_FakeTradeMap.find(m_ExchangeID) == m_FakeTradeMap.end() 成立了。

GetSettlePosiInfo 函数直接返回,这说明,从 m_FakeTradeMap 这个名字可以看出,应该是与伪造成交记录有关的,故查看 FakeForceCloseTrade 函数,发现 pFCOrderVec->size() == 0 成立,即 newFCOrderVec->size() 为 0,故直接定位到 ReqForceCloseCalc 函数中修改更新 newFCOrderVec 的部分,通过 gdb 可以发现,reqVecIter->SequenceNumreqMapIter->second.size() 均为 1,故 newFCOrderVec 没有被修改,因此,我们找到了问题所在。

SequenceNum 应该是我勾选的推荐强平单左边的序号再减一。

reqMapIter = m_ReqForceCloseCalMap.find(idx) 这一定义可以看出,reqMapIter 应该是我们勾选的要进行试算的强平单的集合的迭代器,因此,要修改这个问题很简单,删除 if 判断即可,这个 if 判断本来就没啥意义,因为更新 newFCOrderVec 就是用 reqMapIter 来迭代的。

这里,为了保险起见,我们在注意一下 tmpStruct = (orderMapIter->second)[reqVecIter->SequenceNum.getValue]

可以看到 orderMapIterm_InvestorRspGenFCOrderMap 的迭代器,而 m_InvestorRspFCOrderMap 只在 InitRspVec 函数中被调用,这里我们查看 InitRspVec 函数被谁调用了,发现是 ReqGenForceCloseOrder 函数中,执行了 InitRspVec(idx, pRspVec)InitRspVec 的参数其中有一个是 vector<A> *& p,即 vector 的指针的引用,在 InitRspVec 函数中,m_InvestorRspGenFCOrderMap 的值是一个 vector<A>,而这个值的指针被赋给 pRspVec,执行完 InitRspVec 之后,就能通过修改 pRspVec 指向的地址中的内容修改 m_InvestorRspGenFCOrderMap 了,从变量定义可知,该 map 是用于存放推荐强平后的强平单,即点击“强平单生成”之后得到的推荐强平单列表。

总结与反思 链接到标题

这次 bug 的定位与修复耗时大约两个人日,比较久,一是对业务以及业务代码还是不太了解,比如强平试算、生成强平单,是按照什么标准来进行的,管理端的各个选项分别对应代码的什么;二是对变量的功能以及什么时候被更新不熟悉;三是 gdb 太久没用了,不熟悉,还有就是代码的一些特殊性,比如打好断点之后运行函数应该执行 r 1 而不是 rstart_file 脚本会自动添加环境变量,所以自己应该在 .bashrc 中添加环境变量,部署风控后台也不熟悉。

总之,要多熟悉 gdb 的使用,操作风控管理端时,要注意按下按钮会执行代码中的哪个或者哪些函数,输出的结果与代码中哪些变量有关,还有就是代码中虚函数接口用得很多,要注意函数实现到底在哪里。

代码中虚函数接口用得很多,要注意到底调用的哪个子类的函数。

B 端强平单生成列表只有一条 链接到标题

  • 伟正注释掉了 StandardProtoEngine.cpp​ 的 OnRequest​中 case RMS_TID_ReqForceCloseOrder​ 和 case RMS_TID_RspForceCloseOrder​ 两个部分,为什么?

HandleResponese​ 处,添加 TransFieldtoBSField​,该模板函数调用 CopyFieldToBS​,从而将 CRmsNotifyMsgInputField​ 中的 Content​ 的编码由 gbk 修改为 utf-8。

B 端风险通知乱码 链接到标题