DHLibxls/DHxlsReader libxls PSXLSReader 溢出闪退和数据错乱问题
最近我们的 iOS APP 遇到较多闪退的问题,崩溃在 xls 解析相关代码,经过分析应该是使用的解析库 DHLibxls/DHxlsReader
内部的问题。
初步分析
先看崩溃栈,部分信息如下
1 | SIGSEGV |
可以看出是用来解析 xls 的库 DHlibxls/DHxlsReader
发生了闪退,而闪退的位置实际位于其使用的 libxls
库。
留意 DHlibxls/DHxlsReader
用的 libxls
并不是我去查信息时的master
分支,当然最新的master
分支也有问题,以下会说明。
直接看崩溃的实际代码,基本可以断定是溢出导致的。
1 | // typedef uint16_t WORD; |
shortVal
方法所在代码
1 | // DHxlsReader 用的 libxls 分支 |
很显然,master
分支已经留意到,当处理成 short
(-32,768~32,767)类型之后,超过 32,767 的 unsigned short
会被处理成负数,进而引发溢出崩溃。
所以改动之一即是参考 master
的改动,把 shortVal/xlsShortVal
入参和出参类型都改成 unsigned short/uint16_t
。排查下来,这个方法调用的地方,入参基本都是 WORD/uint16_t
,这个改动是合理的。
留意不能只对报错的地方(字符串表下标溢出)做修改,否则还会遇到行数溢出的崩溃问题,代码里有很多的地方都调用 shortVal/xlsShortVal
了,包括行数,所以直接修改这个方法才是正确的。
新的问题
此处修复完成之后,崩溃问题得到解决。但回测发现对于大表格,数据发生了错位,似乎取错了 cell 的数据,排查之后发现原因其实还是在于之前崩溃这一行。
1 | asprintf(&ret,"%s",pWB->sst.string[shortVal(*label)].str); |
这一行是获取对应的cell的数据,而下标是字符串表的位置。众所周知,xls 行数最大是支持到 65535 的,意味着 cell 的个是会超过 65535(unsigned short
上界)的 ,即字符串表原则上应该也会超过 unsigned short
上界。所以虽然修复了崩溃的问题,但只是不再为负数了,一旦超过 65535,就会从 0 开始重新计数,进而发生错位现象。
先排查下调用栈。
1 | // 原崩溃的代码位置 |
虽然 LABEL
类型里定义的是 BYTE value[1];
,但实际使用是转成了 WORD_UA/uint16_t
,到底是个多大的存储,由外侧读取的地方才能知道。根据以上代码,可以知道的是,报错的地方是 LABELSST
类型的 cell。
查阅了下微软的xls官方文档/PDF,关于 LABELSST
的内存布局描述,大约在 PDF 的 326 页左右,摘抄部分信息
2.4.149 LabelSst
The LabelSst record specifies a cell that contains a string.cell (6 bytes): A Cell structure that specifies the cell containing the string from the shared string table.
isst (4 bytes): An unsigned integer that specifies the zero-based index of an element in the array of XLUnicodeRichExtendedString structure in the rgb field of the SST record in this Workbook Stream ABNF that specifies the string contained in the cell. MUST be greater than or equal to zero and less than the number of elements in the rgb field of the SST record.
其中这个 6 bytes 的 cell
对应就是代码里的 LABEL
类型前面3个部分,之后的 isst
则对应后面的 value
部分,用来从字符串表取下标找字符串。文档指出是 4 bytes 的 unsigned integer
类型,所以用 WORD_UA/uint16_t
来处理是不对的,应当用 uint32_t
来处理。
所以另一个需要修的地方,就是将取下标处的类型转换,改为 uint32_t
.
1 | // 原代码 |
总结
先总结下改动,一共需2处:
- 原
shortVal
方法的入参和出参,都从short
改为unsigned short
。此处改动能解决 cell 过多、row 过多等引起的溢出闪退问题。 - 原
xls_getfcell
方法内,对于XLS_RECORD_LABELSST
类型的 cell 处理,字符串表的下标的类型转换,从unsinged short / uint16_t
改为uint32_t
类型。此处能解决 cell 数量过多时读取 cell 数据错位的问题,准确的说是字符串类型的 cell 过多时。
另外,排查过程中,我们也尝试使用 PSXLSReader 库,这是一个从 DHlibxls/DHxlsReader fork 出来优化过的库,发现有完全一样的闪退问题和错乱问题。检查源码,发现以上2个问题同样存在。使用 DHlibxls/DHxlsReader、libxls、PSXLSReader 进行 xls 解析的同学,如果发现有类似闪退或 cell 错乱问题,可以参考此文档进行排查和修复。
对于 iOS ,我们的实际修复方案是 fork PSXLSReader 仓库到本地,修复完成后,源码集成。
另考虑到上述几个仓库最近一次更新时间都比较早了,暂时没有提 PR 到相关的仓库。