Delphi Data.DBXEncryption 整数溢出,关闭检查为何仍报错?
结论
先检查主程序实际使用的 Data.DBXEncryption 编译产物,确认该单元编译时是否开启了溢出检查。关闭主程序的 Overflow checking 不会改变已经编译好的 DCU 或运行时包中的机器码;在调用方添加指令,也不会改变被调用单元中已有的检查代码。
要显式关闭溢出检查,应使用 {$Q-} 或 {$OVERFLOWCHECKS OFF}。仅添加 {$Q} 不能说明检查已经关闭。
目前还无法确定异常由哪一份文件或配置引起。简化程序正常而主程序异常,可以据此对照两者的编译配置和依赖来源,但不能直接认定 Delphi 安装损坏或密码错误。
为什么同一行乘法会表现不同?
触发异常的运算发生在 TSQLConnection.Connected := True 之后:
if Ax <> 0 then
Ax := Ax * Bx;
函数此前将 Bx 设为 20021。Ax <> 0 排除了零值,乘积仍可能超出 Integer 的取值范围。
Delphi 的 $Q 控制编译器是否生成整数溢出检查代码。开启检查后,乘法等运算会附带检查,失败时抛出 EIntOverflow。是否生成检查代码在编译时决定。官方说明:Overflow checking
因此,即使两个程序显示的 Pascal 源码相同,执行的机器码也可能不同。调试器显示了某个 .pas 文件,也不能说明该文件刚刚参与过编译。
函数中连续出现乘法、加法和 xor,从代码形态看,它可能依赖固定宽度整数的回绕运算。缺少完整实现和兼容性验证,不能认定所有溢出都可以忽略。
按顺序排查编译配置和文件来源
1. 确认修改的是当前配置
先核对主程序和简化程序当前实际使用的设置:
- Build Configuration,例如 Debug 或 Release。
- Target Platform,例如 Win32 或 Win64。
Overflow checking的最终有效值,包括继承和覆盖关系。Use debug .dcus、项目 Search path、全局 Library path。- 是否使用运行时包,以及使用了哪些包。
SQLConnection 的属性和参数一致,只能说明这部分连接设置一致,不能说明两个程序链接了相同的代码。
2. 检查指令所在的单元
在需要编译的目标单元中,可以使用以下指令显式关闭检查:
{$Q-}
或:
{$OVERFLOWCHECKS OFF}
这些是局部编译指令。如果只将它们写在主程序 .dpr 或调用 TSQLConnection 的业务单元中,不能推断 Data.DBXEncryption 已使用相同设置重新编译。
还要检查目标单元及其包含的 .inc 文件,确认之后是否有生效的 {$Q+} 或 {$OVERFLOWCHECKS ON}。
如需判断某个源码位置的编译状态,可以在受控的源码副本中,在目标函数定义前临时加入:
{$IFOPT Q+}
{$MESSAGE WARN 'Overflow checking is ON at this location'}
{$ENDIF}
出现警告,说明编译器处理该位置时检查处于开启状态。没有警告不能单独证明检查已关闭,也可能是这份源码没有参与编译。
3. 清理项目输出,再执行 Build
DCU 是单元源码的编译产物,应用可以直接链接现有 DCU。修改项目选项后,需要确认相关单元确实重新生成。官方说明:Delphi Compiled Unit File
建议依次执行:
- 备份当前项目和配置。
- 清理当前配置、当前平台下的项目生成文件。
- 执行 Build,检查编译输出。
- 确认启动的是这次生成的 EXE。
- 核对
Data.DBXEncryption来自哪个目录,是否存在项目目录中的旧副本。
清理范围应限于可以重新生成的项目输出,不要直接删除 Delphi 安装目录中配套提供的 DCU。
Build 不保证所有库都从源码重新编译。若使用的仍是预编译单元,需要继续确认该 DCU 的来源。
4. 若使用运行时包,检查实际加载的 BPL
如果 Data.DBXEncryption 位于运行时包中,重建 EXE 不会自动改变已有 BPL 内的代码。BPL 承载包的运行时代码,DCP 提供编译所需的信息。官方说明:Packages
可以通过调试器的模块列表核对实际加载的包路径,比较两个程序是否加载了不同目录中的版本。修复时需要使用同一 Delphi 版本配套的包和编译产物,避免混用旧文件。
此前曾恢复 System.Classes 并重装部分单元,因此值得检查文件来源和配套关系,但这段经历本身不能证明根因。
确认依赖回绕运算后,如何处理?
优先恢复与 Delphi 10.2.3 安装配套、来源明确的库文件,再验证连接行为。
如果确认目标实现需要回绕运算,而且当前构建确实错误地为它启用了溢出检查,可以在受控的单元副本中把 {$Q-} 放在相关函数定义之前,再重新编译实际承载该函数的单元或包。修改范围结束后,应恢复原先的检查状态:原来开启时恢复为 {$Q+},原来关闭时保持关闭。
IncrAfter(Ax) 是另一个方法调用,也需要留意。如果异常随后出现在该方法内部,应独立检查它的编译状态;调用方的指令不会在运行时传递过去。
不要直接把全部 Integer 改成 Int64,也不要捕获异常后继续连接。改变位宽可能改变算法状态和输出;函数在异常发生前已经修改了 FSi,吞掉异常也不能恢复正常执行结果。
怎样判断排查是否完成?
修复后,应同时确认目标乘法不再抛出异常、连接能够完成、原有凭据能够按预期工作,并检查后续乘法和加法是否还会报错。
如果确认运行的是最新产物,且目标函数确实按 {$Q-} 编译,仍在同一位置报告整数溢出,应重新核对异常类型、调用栈、机器码对应位置及源码匹配情况,再检查进入函数时的状态值。此时继续反复切换项目选项,通常无法提供新的线索。
备注:内容仅供参考。
