 与 FixedString 兼容性、无调试符号二进制的栈回溯)
ClickHouse v24.1.2.5-stable 关键 Bug 修复解析translate() 与 FixedString 兼容性、无调试符号二进制的栈回溯【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse本文聚焦 ClickHouse 官方稳定版 v24.1.2.5-stablebuild b2605dd4a5a相比上一稳定版 v24.1.1.2048-stable 所包含的两项用户可见 Bug 修复其一为字符串函数translate()在FixedString输入下的返回类型错误可能导致崩溃其二为不带调试符号的二进制文件中栈回溯stacktrace质量低下的问题。通过结合 translate 函数实现、符号索引实现 与 回归测试读者可以理解这两个问题产生的根源、修复后的代码路径以及如何在当前仓库中验证修复效果。版本背景一个典型的快速修复稳定版v24.1.2.5-stable 是 ClickHouse 2024 年 1 月分支中的一个补丁稳定版。与一般功能迭代版本不同这类v24.1.x.y-stable补丁版只合入用户可见的 bug 修复user-visible misbehavior in an official stable release并走 backport回溯移植流程把修复从主干安全地带回稳定分支。该版本仅包含两项修复修复对应 PR/Issue影响translate()处理FixedString输入时返回类型错误可能导致崩溃由 ClickHouse Bug Bounty Program 参与者 YohannJardin 发现backport 于 #59425字符串翻译函数在定长字符串列上的类型契约被破坏无调试符号二进制下的栈回溯修复backport 于 #59478排障诊断信息质量下降难以定位崩溃点下文分别深入这两项修复的源码与测试依据。修复一translate() 在 FixedString 输入下返回 String 而非 FixedStringtranslate() 的函数语义translate(s, from, to)是 ClickHouse 的字符串替换函数按一对一字符映射规则工作from与to长度相等时s中出现的from第 i 个字符被替换为to的第 i 个字符当from比to长时from末尾没有对应替换字符的那些字符会从s中被删除。该函数的完整语义、示例与文档说明可以在 translate.cpp 的函数注册段 中找到官方文档描述为Replaces characters in the stringsusing a one-to-one character mapping defined byfromandtostrings.fromandtomust be constant ASCII strings.例如SELECT translate(Hello, World!, delor, DELOR)得到HELLO, WORLD!SELECT translate(clickhouse, clickhouse, CLICK)得到CLICK多余的映射字符被删除。问题本质返回类型与输入类型脱节translate接受的第一参数可以是String也可以是定长字符串FixedString(N)。修复前的问题在于当输入是FixedString列时函数内部走的是变长String的生成路径返回了一个String列而调用方预期的是与原输入类型一致的FixedString。这种类型契约破坏的后果是列的数据布局与类型描述不一致后续算子按FixedString的定长语义处理实际为变长的数据时可能访问越界直接表现为查询崩溃因此被列为「官方稳定版中的用户可见错误行为」级别 Bug并通过 ClickHouse Bug Bounty Program 被发现和报告。从修复后的当前源码可以确认正确行为是「返回类型跟随输入类型」。在 getReturnTypeImpl 中返回值类型直接取第一个参数的类型return arguments[0];——输入是FixedString则返回FixedString输入是String则返回String。而 executeImpl 则针对两种列类型分别分派命中ColumnString时调用Impl::vector(...)变长路径命中ColumnFixedString时调用Impl::vectorFixed(...)定长路径并原样保留列的长度属性ColumnFixedString::create(col_fixed-getN())。正是这条FixedString分支的补全让函数在修复后返回与输入一致的定长列。定长路径的实现细节TranslateImpl::vectorFixed见 translate.cpp与变长路径有几处关键差异长度约束更严格它要求map_from.size() map_to.size()否则抛出BAD_ARGUMENTS。原因在于FixedString(N)必须保持输出长度恰好为 N 字节变长路径里「from比to长时删除多余字符」的语义会改变字符串长度在定长列中无法表达逐字节原样映射对每个字节若为 ASCII ascii_upper_bound即\x7f则按映射表替换否则原样拷贝保证输出长度恒等于输入长度尾随\0保留FixedString的语义是固定 N 字节、不足部分以零字节填充vectorFixed逐字节处理因此天然保留这些填充字节。需要特别注意的是translate的 UTF-8 变体translateUTF8不支持FixedString输入在 TranslateUTF8Impl::vectorFixed 中直接抛出BAD_ARGUMENTS异常说明这是该变体的有意限制UTF-8 变长编码无法保证定长输出。回归测试验证仓库为此修复配套了专门的回归测试 02981_translate_fixedstring.sqlSELECT translate(aaa::FixedString(10), a,A); SELECT translate(number::String::FixedString(1), 0,A) from numbers(4);第一句把常量 aaa 显式转换为FixedString(10)再翻译第二句把 0~3 这 4 个数字逐个转成单字节FixedString(1)后把 0 翻译成 A。对应期望输出 02981_translate_fixedstring.reference 为AAA\0\0\0\0\0\0\0 A 1 2 3可见aaa::FixedString(10)翻译后仍是 10 字节的定长值AAA后跟 7 个\0填充字节——类型、长度与填充语义全部正确这正是本次修复要保证的行为。此外仓库中还有 02353_translate.sql 等常规用例覆盖translate在String输入下的映射、删除与 UTF-8 场景。修复二无调试符号二进制下的栈回溯栈回溯在 ClickHouse 中的作用ClickHouse 在异常处理、信号处理与内存追踪等多个环节都会生成栈回溯用于崩溃诊断和错误定位。例如 StackTrace 实现 在 Linux 上依赖libunwind完成栈展开见 StackTrace.cpp 的#include libunwind.h随后把展开出的地址交给 SymbolIndex 解析成函数名与源码位置最终写入异常信息或日志。符号来源的三层结构SymbolIndex.cpp 的文件头注释 系统说明了二进制中符号信息的三个来源这也是理解本修复的钥匙程序头动态符号表program headers / dynamic symbol table仅含用于动态链接的导出符号无论是否加-g都存在。ClickHouse 因为以-rdynamic链接导出大多数符号即使没有调试信息也能提供足够好的栈回溯ELF 符号表.symtab/.dynstr由collectSymbolsFromELFSymbolTable解析可能因链接选项而缺失部分内部链接符号DWARF 调试信息信息最完整能给出源码文件名与行号但只在开启-g时存在。修复前的短板当二进制不带调试符号例如发行版默认的clickhouse二进制或用户未安装clickhouse-common-static-dbg包时栈回溯退化为只能得到导出符号级别的地址解析addr2line、gdb等工具也无法把地址映射到具体源码行。修复 #59444 正是针对这一点让无调试符号的二进制也能生成更完整、可用的栈回溯。从修复后的 SymbolIndex.cpp 可以看到collectSymbolsFromELF已经实现了分离调试信息文件的自动发现依次探测与主二进制同目录的*.debug文件local_debug_info_path见 L388-L406系统标准路径/usr/lib/debug/binary相对路径.debug见 L391基于 Build ID 的路径/usr/lib/debug/.build-id/xx/xxxx....debug见 L411-L426。并且会比较 Build ID 来确保分离的调试文件与当前加载的二进制匹配见 L442 的校验逻辑不匹配时回退到主二进制的符号解析避免拿错调试信息。配合发行版clickhouse-common-static-dbg包把调试信息拆分到/usr/lib/debug/usr/bin/clickhouse.debug的做法注释见 L45-L46用户在崩溃现场即使只装了精简二进制也能在安装 debug 包后立即获得带文件名和行号的完整栈回溯。从源码结构看的修复效果结合当前代码可以推断本次修复后符号收集从「仅依赖当前二进制自身」扩展为「当前二进制 多路径分离调试文件 Build ID 校验」的完整链路无调试符号的二进制至少能稳定给出导出符号级回溯装上 debug 包后无需重装主程序即可升级到源码行级诊断信息栈回溯相关的StackTrace、Exception等组件见 Exception.cpp在符号解析端变得更健壮不再因缺少调试信息而输出近乎无意义的地址列表。如何在当前仓库中验证这两项修复对translate()修复可以直接运行仓库自带的回归测试目录中的用例执行tests/queries/0_stateless/02981_translate_fixedstring.sql并对照 reference 文件即可确认FixedString输入下返回类型与填充语义正确。也可用clickhouse-local快速手工验证SELECT translate(aaa::FixedString(10), a, A); -- 期望AAA\0\0\0\0\0\0\0保持 10 字节定长对栈回溯修复可以构建一个不带调试符号的clickhouse二进制触发崩溃如非法内存访问观察日志中的栈回溯能否给出符号化函数名再安装或放置对应的分离调试文件后复现同一崩溃对比回溯是否升级到含源码文件与行号。注意本修复在无调试符号时能恢复的是「导出符号级别」的可用回溯完整的源码行定位仍依赖调试信息文件这是 DWARF 机制本身的固有前提。小结v24.1.2.5-stable 的两项修复虽然条目简短但都触及 ClickHouse 的核心健壮性translate()修复保证了字符串函数在FixedString定长列上的类型契约不被破坏杜绝了潜在的崩溃路径栈回溯修复则让无调试符号的二进制在诊断时不再抓瞎并通过 Build ID 校验机制安全地复用分离调试文件。二者的源码与测试证据都保留在 src/Functions/translate.cpp、src/Common/SymbolIndex.cpp 与 tests/queries/0_stateless/ 中可供深入研读与复现验证。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考