尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
一文搞懂栈保护指令:从原理到工程实践
我们经常在安全公告和漏洞分析里看到栈保护这个词但真让自己去编译一个项目、决定要不要开、开哪个级别时很多人其实心里没底。尤其是现在主流的 C/C 编译器都内置了以指令选项形式存在的栈保护机制比如大家常听到的栈金丝雀stack canary、-fstack-protector这一系列选项它们的原理是什么、彼此有什么区别、实际工程里到底怎么选这些才是真正值得花时间搞清楚的地方。这篇文章我打算把栈保护指令从原理到实践完整拆一遍。内容围绕几个核心问题展开栈溢出为什么会成为经典攻击入口、编译器插入的栈保护指令到底在保护什么、-fstack-protector系列三个级别有何区别、如何用工具验证一个二进制是否开了保护以及真实项目中容易踩的坑。适合正在做 C/C 项目加固的开发者、对系统安全感兴趣的同学也适合那些被安全扫描报告搞得一头雾水、想知道这个告警到底重不重要的运维和研发。我尽量用实际工程里会遇到的场景来讲而不是教科书式的定义堆砌。这样你读完不仅知道选项怎么填还能真正理解背后的取舍逻辑。1. 栈保护指令到底在保护什么1.1 栈溢出的底层原理回顾要说清楚栈保护指令的来龙去脉必须先回到栈溢出的本质上来。一个 C 语言的函数调用链是长这样的调用方把参数压栈、执行call指令时处理器会把返回地址也就是调用指令的下一条指令地址压入栈中然后被调函数的栈帧开始扩张局部变量往上排列。问题就出在这个排列方式的脆弱性上如果程序往一个局部数组里写入的数据超出了数组边界多出来的字节会顺着栈地址的增长方向一路冲刷到栈帧底部保存的ebp/rbp基址指针和返回地址。这里有个关键点x86 的栈是从高地址向低地址增长的而数组元素是顺序排列的从低地址向高地址填充。你往数组里写入的数据越界后会覆盖掉它后面的局部变量然后是旧栈帧指针最后是返回地址。攻击者精心构造的溢出数据可以精确改写返回地址把程序的控制流劫持到一段恶意代码上去。经典的strcpy、gets、sprintf这类不安全的库函数就是最常见的帮凶因为它们不做边界检查或者说它们压根没有边界参数可以检查。很多教材喜欢用缓冲区溢出这个词但从防御角度讲我们真正在意的是返回地址是否被改写。栈保护指令的思路就是从这个角度出发的在返回地址前面人为地放一个哨兵值函数返回前检查这个哨兵有没有被改掉。这个哨兵值就是栈金丝雀也就是 stack canary。之所以叫金丝雀是因为早期煤矿工人会带金丝雀下矿井金丝雀对有毒气体极其敏感一中毒就死提醒矿工赶紧撤。栈保护里的金丝雀承担的就是这个角色——它牺牲自己检测到被改写就触发崩溃保全整个进程的控制流完整性。1.2 编译器如何把保护织进二进制栈保护指令并不是操作系统或者硬件提供的功能而是编译器在生成汇编代码时自动插入的检查逻辑。以主流的 C/C 编译器为例当你启用-fstack-protector相关选项时编译器会对符合条件的函数做两件事第一在函数序言prologue部分从线程局部存储TLS中取一个随机数把它写入到当前栈帧的canary位置这个位置在返回地址和局部变量之间。第二在函数尾声epilogue部分在真正的返回指令执行之前先比较栈帧里保存的 canary 值和 TLS 里那个原始值是否一致。如果不一致说明缓冲区溢出已经发生了立即调用__stack_chk_fail函数终止进程。这个机制的精妙之处在于攻击者要改写返回地址必然要越过 canary 所在的位置所以要么连 canary 一起改写要么绕过它。而 canary 的值是进程启动时通过安全随机数生成的攻击者在触发溢出前并不知道它的具体数值胡乱改写就会导致检查失败程序直接崩溃。对于大多数攻击来说让目标进程崩溃已经意味着攻击失效了因为漏洞利用的核心是让程序继续跑攻击者的逻辑而不是让程序退出。提示栈金丝雀保护的核心目标就是让漏洞无法被利用而不是让漏洞不发生。漏洞代码该存在还存在于源码层面但攻击者无法稳定地劫持控制流这才是它的价值。2. 三个栈保护级别指令的差异与选型2.1-fstack-protector系列三个级别对比编译器提供的栈保护指令不是一刀切的而是分了几个级别。这几个级别的差异主要体现在范围上哪些函数会被插入 canary 检查、哪些函数会被放过、性能开销有多大的不同。以 GCC 系编译器为例Clang 也采用类似的兼容选项常见的是这三个编译选项生效函数范围性能开销典型适用场景-fstack-protector仅保护含有 8 字节及以上可变长度数组或 alloc 类函数调用缓冲区的函数较小存量项目快速加固尽量少影响性能-fstack-protector-strong保护所有含局部数组、含地址泄漏到寄存器、含取局部变量地址并传递到外部函数的函数中等大多数新项目推荐兼顾覆盖率和性能-fstack-protector-all保护所有函数无论是否有缓冲区较大对性能不敏感、且安全要求很高的场景如解析器、密码学处理细看就会发现-fstack-protector的判定条件比较保守它只针对函数内声明了长度超过某个阈值的局部数组或者函数调用了alloca这类动态分配栈内存的函数。如果一个函数只有几个指针变量、没有数组哪怕它内部发生了指针误写导致栈帧损坏这个级别也不会管。而-fstack-protector-strong把覆盖范围扩大了只要函数内存在取局部变量地址的操作、或者局部变量被传递给了其他函数编译器认为这可能导致地址泄漏进而被利用同样会被插入保护。-fstack-protector-all则是无差别全覆盖所有函数都加。2.2 不同级别的覆盖范围背后逻辑理解三个级别的差异关键在于理解编译器是怎么判断这个函数值得保护的。-fstack-protector的原始设计年代比较早当时机器性能弱编译器设计者认为只有包含真实缓冲区即可能发生栈溢出的载体的函数才值得付出检查和 TLS 访问的开销。但随着攻击技术的演进漏洞不一定要发生在本地数组上一个函数如果接受了外部传入的指针并且指针指向栈上的数据它也可能成为攻击的跳板。-fstack-protector-strong的引入就是为了解决这个覆盖盲区。它会检查以下条件函数内声明了任何局部数组地址被传递给其他函数函数的局部变量被取地址var后传给了外部调用函数内出现了某种形式的数组操作。换句话说编译器的态度从出了事才管变成了有风险苗头就管。我在实际项目中做过对比一个中等规模的服务端程序使用-fstack-protector时生成的二进制里只有不到 20% 的函数被插入了 canary 检查切换到-fstack-protector-strong后这个比例涨到了 70% 左右而-fstack-protector-all几乎是 100%。运行时性能方面对于 I/O 密集型的网络服务strong 和基础款之间几乎感知不到差异毕竟单次 canary 检查和一次 TLS 读取的开销在纳秒级别在系统调用的开销面前可以忽略但对于纯 CPU 计算的场景比如加密解密、编解码循环all 级别的额外开销就能在性能剖析图里看出明显占比了。2.3 真实项目的选择建议我的建议是新项目直接上-fstack-protector-strong它是在当前安全环境下最均衡的选择既不会像 all 那样让每个函数都承担开销又比基础款覆盖面广得多。对于已经收不了很多遗留代码的存量项目如果你的性能基准测试显示 strong 级别带来不可接受的下降这种项目通常是极端性能敏感的比如高频交易引擎或者视频编解码库可以退到基础款-fstack-protector做妥协。但要注意这个妥协是有代价的基础款管不到只有指针操作、没有本地数组的函数而这些函数如果存在逻辑漏洞导致栈被破坏你连就地崩溃的机会都没有。-fstack-protector-all我一般只在特殊场景用比如一个组件是解析不可信输入的网络数据包解析器、文件格式解析器等而且函数数量不多、性能余量充足我会单独对这个组件开启 all 级别而不是整个项目统一配置。在 CMake 里可以按目录粒度设置编译选项这是很实用的精细化加固手段。3. 栈金丝雀的原理与绕过边界3.1 canary 的生成、保存与检查流程实际看一下编译器生成的汇编代码会直观很多。一个简单的函数void check_input(const char *buf) { char local[16]; strcpy(local, buf); use(local); }开启-fstack-protector-strong编译后汇编层面的函数入口通常长这样push rbp mov rbp, rsp sub rsp, 32 mov rax, qword ptr fs:[0x28] ; 从 TLS 读取 canary 值 mov qword ptr [rbp-8], rax ; 保存到栈帧 canary 槽位 xor eax, eax ; 清零寄存器而函数返回前的检查段是mov rax, qword ptr [rbp-8] ; 取出栈上保存的 canary xor rax, qword ptr fs:[0x28] ; 和 TLS 里的原始值比较 jne .fail ; 不一致则跳转到失败处理 ...正常返回... .fail: call __stack_chk_failplt这里的fs:[0x28]是 x86-64 下线程局部存储中的一个固定偏移存的就是进程启动时生成的随机 canary 基准值。注意它存在 TLS 里所以每个线程都有一份独立变量但这不意味着每个线程的 canary 值不同——实际上 canary 基准值通常是同一个只是存在不同线程自己的 TLS 里防止某些攻击通过任意地址读写去跨线程修改。canary 的随机性来源很有意思早期实现里canary 是一个固定值0x000a0ff以换行符和空字节结尾目的是对抗strcpy这类会在第一次遇到空字节时停止拷贝的字符串函数。如果 canary 的第一个字节是0x00那strcpy、sprintf这类函数在覆盖到 canary 时就会立即看到空字节并停止写入天然形成一道屏障。现代 Linux 系统的 canary 则来源于安全随机数生成器在进程启动时通过内核获取不可预测性大幅提升。但第一个字节依然是空字节这是有意的设计保留对字符串操作的天然截断效果。3.2__stack_chk_fail触发后的行为当 canary 校验失败时编译器生成的代码会调用__stack_chk_fail。这个函数的工作原理是通过stdout/stderr输出报错信息通常类似*** stack smashing detected ***: program terminated然后调用abort终止进程。有些嵌入手套件会自定义这个函数改成记录日志后重启设备或者只踢掉当前任务而不必重启整个系统。这里有一个容易忽略的工程细节如果你在编译时开启了符号剥离-s参数__stack_chk_fail报错信息里你的程序名会自动带上但堆栈回溯信息会非常有限。调试阶段建议关闭符号剥离保留-g调试信息这样 canary 崩溃时用 core dump 可以直接看到函数的调用栈定位到具体是哪一个函数在返回时被破坏的。从攻击者视角看canary 保护的强度取决于它是否可被读取。如果程序存在一个信息泄漏漏洞比如一个越界读把 canary 的字节打印出来了攻击者就可能在后续的溢出攻击中精确恢复 canary 值绕过保护。这在真实漏洞利用中被称为读溢出配合写溢出的信息泄漏加栈滥用的组合利用链。所以栈保护指令并不是一劳永逸的银弹它显著提高了利用难度但配合信息泄漏仍然可能被攻破。3.3 什么情况保护会失效栈保护指令有很明确的失效边界理解这个边界能帮你避免盲目自信。第一canary 覆盖链中涉及的 TLS 值如果被篡改——虽然一般攻击者很难做到但配合任意地址写漏洞直接改掉 TLS 里的 canary 基准值就能让检查失效。第二小范围溢出不触发检查canary 检查只发生在函数返回时攻击者如果只覆盖了 canary 之后、返回地址之前的一段数据比如只覆盖了局部变量区域那在函数返回前 canary 依然是完整的检查不会失败但局部变量已经被污染了。这种非控制流破坏的攻击可以改变程序逻辑判断绕过安全校验并不需要劫持返回地址。这也是为什么栈保护不能取代其他缓解措施的原因。4. 从编译到运行完整观测一次栈保护生效4.1 用 readelf 和 checksec 验证保护开关理论知识再丰富也不如实际动手验证一次。我建议你准备一个有缓冲区的简单函数分别用不同的保护选项编译然后检查生成的二进制。检查方法不需要什么特殊工具Linux 下常用的是readelf直接查看段信息里的.note.gnu.property或者查找相关符号gcc -fstack-protector-strong -o demo demo.c readelf -s demo | grep __stack_chk_fail如果二进制里出现了__stack_chk_fail的未定义引用通常是动态链接符号说明编译时确实插入了 canary 检查逻辑。还能用objdump -d demo搜索fs:[0x28]的访问指令objdump -d demo | grep -n fs:0x28能搜到 TLS 读取指令就说明实际生效了。很多发行版会内置checksec脚本它对 PE/ELF 做一键检测直接显示栈保护、PIE、RELRO、NX 这些属性。检测比较方便但在脚本不可用的环境里readelf是万能的。4.2 通过反汇编定位序言尾闾代码拿到一个你不太确定是否开启保护的第三方二进制时最快的判断方法就是反汇编找金丝雀。用 objdump 反汇编某个函数如果在函数序言里看到了对fs:[0x28]的读取、把它存储到栈上某个偏移位同时在函数结尾看到了对应的比较和条件跳转那这个函数就是被保护的。一个典型带检查的函数反汇编特征4005a0: push %rbp 4005a1: mov %rsp,%rbp 4005a4: sub $0x20,%rsp 4005a8: mov %fs:0x28,%rax 4005ad: mov %rax,-0x8(%rbp) 4005b1: xor %eax,%eax ... 4005d0: mov -0x8(%rbp),%rax 4005d4: xor %fs:0x28,%rax 4005dd: jne 4005f0 __stack_chk_fail看到这个结构基本实锤了。有一些加壳或者混淆的二进制可能把这个特征隐藏掉了正常编译产物里都能直接看到。4.3 制造一次金丝雀触发再复盘用样例代码实际触发一次能让你直观感受失败现场。下面这段代码故意用memcpy越过局部缓冲区边界把 canary 槽位冲掉#include stdio.h #include string.h void vulnerable(const char *input) { char local[8]; strcpy(local, input); printf(local: %s\n, local); } int main(int argc, char **argv) { if (argc 1) { vulnerable(argv[1]); } return 0; }编译时开启 strong 保护gcc -fstack-protector-strong -g -o canary_demo canary_demo.c ./canary_demo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA程序会输出类似*** stack smashing detected ***: program terminated然后异常退出。用ulimit -c unlimited开 core dump 再用 gdb 打开执行bt命令就能看到一个非常清晰的调用栈。注意一点canary 检测到崩溃的位置一定是在vulnerable函数的返回指令之前而不是在strcpy执行的那一刻这在定位漏洞原因时是个重要线索报错的函数可能不是真正发生写的函数而是被破坏栈帧的函数。我把这个过程叫作故障发生点与函数返回点的时差。排查缓冲区溢出问题时如果听到用户在某个函数里报了 stack smashing不要只盯着那个函数看还要往上追一层调用者是否向当前函数传递了超长数据。5. 栈保护指令与相邻缓解措施的关系5.1 FORTIFY_SOURCE栈保护指令是最佳搭档栈保护指令主要防的是函数返回时返回地址被改写但很多溢出发生在函数内部还没等到返回检查程序就继续执行了危险逻辑了比如已经用被污染的数据做了判断。所以现代编译加固都会同时开启_FORTIFY_SOURCE宏需要配合-O2以上优化才生效。它会在编译期对常见的危险函数strcpy、memcpy、sprintf、read等做缓冲区长度的静态分析和动态加固能推导出目标缓冲区大小的时候自动替换成带边界检查的安全版本比如__strcpy_chk。实际操作里_FORTIFY_SOURCE和栈保护指令是被当成组合拳来用的。栈保护拦截结果性破坏返回地址被改FORTIFY 拦截过程性破坏写入本身就超了。两个都开的时候很多常见类型的溢出在写入阶段就会被拦掉根本走不到 canary 检查那一步。5.2 PIE、RELRO 与 RELRO 的协同作用栈保护指令单独开其实是不够的安全加固是一个体系。PIE位置无关可执行文件让二进制被加载到随机地址配合 ASLR 让攻击者无法提前确定返回地址跳转的目标位置RELRO 把.got表段设为只读防止攻击者改写全局偏移表来劫持函数调用。这几者和栈保护之间没有直接代码层面的关联但在编译器指令层面它们是成组成对出现的。我见过不少新人在自己写 Makefile 的时候只加了一个-fstack-protector结果上线之后被安全扫描工具指出 PIE 和 RELRO 没开。实际上主流发行版的编译规范里通常建议统一上这样一份组合-Wl,-z,relro,-z,now -fstack-protector-strong -D_FORTIFY_SOURCE2 -fPIE -pie每一项解决一个维度的攻击路径没有哪一项能替代其他。如果你做的是面向互联网的服务的编译打包建议直接用发行版的安全编译旗标模板别自己拍脑袋乱配。5.3 影子栈与 CFI更进一步的设想栈保护指令毕竟只能提供单点的检查它不知道这个返回地址是不是调用它的那个函数真正压下去的它只知道canary 没被改。为了对抗更高级的 ROP 攻击链硬件和编译器生态都在向影子栈shadow stack和控制流完整性CFI方向演进。影子栈的思路是在独立的内存区域里额外维护一份返回地址的副本函数返回时用栈上的返回地址和影子栈里的比对不一致立即终止这个方案能防御的信息泄漏加溢出组合攻击的鲁棒性更强。虽然影子栈在部分新 x86 处理器上已经得到硬件指令支持比如 CET 技术里的 shadow stack 部分但它对现有二进制的兼容性、调试器的支持度还有诸多限制目前并没有在所有 Linux 发行版里默认开启。在做技术选型时栈保护指令仍然是最通用、投资回报率最高、兼容性最好的底线性加固。6. 实际工程中遇到的典型问题与排查技巧6.1 编译期undefined reference to __stack_chk_fail这个报错在新搭建的交叉编译工具链环境里非常常见。当你开启了栈保护指令编译器在生成的代码里引用了__stack_chk_fail和有时__stack_chk_guard但是链接阶段如果链接的 crt 库、C 库或最终二进制里找不到这个符号链接就会失败。原因通常是你的工具链缺少对应的库支持或者你链接的是某些静态库而该库本身没有包含这个符号的定义。排查思路很直接用nm查一下你的 libc 或 crt 目标文件里是否有这个符号如果工具链的 sysroot 不完整需要把libssp栈保护支持库加入链接或者换一套编译参数确保-lssp相关的栈保护辅助库被正确链接。嵌入式平台上还有一种做法是自己在板级支持包中实现一个__stack_chk_fail输出错误后执行重启或者点亮状态灯这个在裸机环境下尤其管用。6.2 运行时崩溃但 core dump 看不到关键帧栈保护检查通过后 detach 掉的崩溃现场有时会令人困惑core dump 里只看到__stack_chk_fail的调用者再往下的栈帧信息可能因为栈已经被破坏而无法正常回溯。这时候如果程序开启了-fno-omit-frame-pointer保留帧指针gdb 的向后遍历能力会强很多。我踩过的一个坑是旧代码里使用了setjmp/longjmp跨函数跳转。如果跳转发生在函数序言已经保存 canary 之后、但尾声尚未检查之前longjmp会直接绕过当前的栈帧回到setjmp保存的上下文编译器生成的 canary 检查代码根本不会执行。这种情况下栈上残留的 canary 值会被之后的函数覆盖成随机数据下一次从那个被跳跃的路径调用到相同位置时检查失败的误报就会出现。这是很多人最开始排查时摸不着头脑的幽灵崩溃——它并不是真的发生了缓冲区攻击而是栈帧的异常跳转破坏了检查的配对。6.3 数据速查常见问题与处置建议现象可能原因处置建议链接期报错缺少__stack_chk_fail工具链 sysroot 不完整未链接 libssp补齐库或用-lssp显式链接裸机环境下自行实现该符号运行时高频出现 stack smashing 崩溃代码里存在真实的缓冲区越界写用 ASan地址消毒器重新编译定位越界现场非法使用longjmp导致误报跨函数跳转绕过 canary 尾声检查修复跳转逻辑避免跨可变长度数组栈帧跳转-fstack-protector开启后仍被溢出攻击利用覆盖范围不够或存在信息泄漏升级到-fstack-protector-strong排查越界读启用保护后性能下降明显使用了 all 级别且函数极短调用极频繁评估是否可用 strong 替代或用配置文件按模块控制6.4 排查实战一次奇怪的周期崩溃再分享一次实际排查经历。某个网络服务进程在压测 24 小时后出现周期性的stack smashing detected崩溃重启后又能撑很久。最初怀疑是某个内存越界写但上线 ASan 跑了很久却复现不出来。后来发现崩溃点是某个 XDR 编码函数返回时这个函数接收来自对端的数据长度字段内部循环拼装缓冲区的时候根据这个长度做跳转但有一次长度值异常大导致一次超长拷贝。为什么 ASan 没抓到因为代码恰好从堆上分配了很大的接收缓冲区越界写发生在两个堆块之间的大小空洞里不触发 ASan 的红区直到把某个栈上对象的 canary 槽写坏了才暴露。这个案例给我两个很深教训第一不要因为检查代码里没漏洞就忽略数据来源的异常第二栈保护崩溃提示的元凶函数往往只是受害者真正的写入点在调用链更深的地方需要结合数据流分析来做判断。7. 工程上的指令配置习惯与总结性经验最后聊一聊编译配置层面的个人习惯。我维护的项目里顶层 CMake 通常统一设几个全局编译选项让每个子模块都有一个默认的加固基线而特殊模块例如性能关键的信号处理、视频编解码可以覆盖这些选项。每个模块单独编译时都能看到一个明确的保护配置输出这样在审计产物时可以直接通过二进制反查不用追代码历史。在实际操作中我一般在交叉编译文件或者CFLAGS环境变量里这么配置CFLAGS-O2 -g -fstack-protector-strong -D_FORTIFY_SOURCE2 -fPIE -pie -Wl,-z,relro,-z,now这里-O2配合_FORTIFY_SOURCE2让 FORTIFY 检查达到最大强度同时它也依赖优化才能做边界推导。如果不开优化_FORTIFY_SOURCE的意义就大打折扣。很多初学的人踩过这个坑明明加了-D_FORTIFY_SOURCE2可nm查看二进制时却发现strcpy并没有被替换成__strcpy_chk原因就是漏掉了-O2这个前提。选-fstack-protector-strong而不是-fstack-protector-all的另一个理由是调试验证成本all 级别在 debug 版本下会让.debug信息膨胀、单步执行时频繁在 canary 检查点中断干扰定位问题的效率。strong 级别的覆盖率已经足够让绝大多数真实的栈溢出崩溃在溢出时暴露出来而在调试阶段引入的干扰却少得多。我个人在实际操作中的体会是栈保护指令是一个性价比极高的安全投入它不像代码审计那样需要逐行审查逻辑也不像模糊测试那样要跑大量样本只需要在编译参数里加上几行配置就能让一大批经典的栈溢出攻击手段失效。但它也不是免死金牌配合 FORTIFY、PIE、RELRO 才是完整姿势。你不需要成为一个安全专家才能用对它但你需要理解它保护的边界在哪里这样才能在面对那些诡异的崩溃时快速分辨出这是攻击在撞击防护还是这是代码自身的漏洞暴露。再说一个容易被忽略的收尾习惯正式发布前花十分钟用checksec或readelf -d检查一下关键产物的保护属性把这个检测步骤写进 CI 流程。我见过太多项目在 Makefile 里写了加固选项但因为某个子模块用的是旧编译脚本或者链接了老的静态库导致最终产物根本没带上保护而构建日志里也没有任何报错。这种静默失效比不加保护更危险因为它给你的心理带来错误的安全感。而有了产物级检查之后这个问题就能在每次构建时自动暴露出来。
RELATED

相关推荐

MySQL锁机制实战:从行锁分类到死锁排查全解析

MySQL锁机制实战:从行锁分类到死锁排查全解析

做后端开发这些年,MySQL 锁相关的坑没少踩。平时写 SQL 感觉不到锁的存在,一到并发量上来或者压测期间,线上就会出现接口偶发抖动,日志里不是Lock wait timeout exceeded就是Deadlock found。这时候回头查才知道,锁不是…

📅 2026/10/10 17:03:39
2026软件项目计划书Word框架:从目标到排期与风险管控

2026软件项目计划书Word框架:从目标到排期与风险管控

又到年底,各类2026年的规划任务开始压上来。最近几个朋友都在问同一个问题:软件项目计划书写成Word,到底该搭一个什么样的框架,既能让评审通过,又不会变成几十页没人看的废纸。说实话,计划书这件事&#xf…

📅 2026/10/10 17:03:39
MySQL ONLY_FULL_GROUP_BY报错深度解析:从原理到五种解决方案

MySQL ONLY_FULL_GROUP_BY报错深度解析:从原理到五种解决方案

每次在 MySQL 里执行带GROUP BY的聚合查询,十有八九会被ONLY_FULL_GROUP_BY劈头盖脸教育一顿。尤其在 MySQL 5.7 之后,这个问题几乎成为每个开发者必踩的坑:明明 SQL 逻辑看起来很顺,结果 MySQL 甩过来一个ERROR 1055,…

📅 2026/10/10 17:03:39
MORE NEWS

更多资讯

📰

软件评审检查表:从需求到测试的逐项评审实践指南

简介:这是一份面向软件设计与开发评审场景的实用检查表文档,适合项目经理、架构师、开发人员和质量管理人员使用。文档将评审过程拆解为需求规格说明书检查、概要设计检查和详细设计检查三大模块,覆盖清晰性、完整性、依从性、一致性、可行性…

📰

Cline 实战踩坑实录:Token 烧钱、权限误伤、上下文爆炸,这三座大山怎么翻?

Cline 实战踩坑实录:Token 烧钱、权限误伤、上下文爆炸,这三座大山怎么翻? 【免费下载链接】cline Autonomous coding agent as an SDK, IDE extension, or CLI assistant. 项目地址: https://gitcode.com/GitHub_Trending/cl/cline 开…

📰

AI 时代还需要传统搜索引擎吗?Hister 的 MCP 集成给出了另一种答案

AI 时代还需要传统搜索引擎吗?Hister 的 MCP 集成给出了另一种答案 【免费下载链接】hister Your own search engine 项目地址: https://gitcode.com/GitHub_Trending/hi/hister ChatGPT 式 AI 搜索的爆发,让一个原本不成问题的问题重新摆上台面&…

📰

Visual Basic .NET 控制台编程入门实战:基于 learnxinyminutes-docs 的完整代码教程

文档教程 【免费下载链接】learnxinyminutes-docs Code documentation written as code! How novel and totally my idea! 项目地址: https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs 点击查看 免费下载 本教程以仓库内 zh-cn/visualbasic.md 为核心蓝本…

📰

1.9B 当决策引擎:NeoHorse-1-9B 接入工单分流的最小实现

1.9B 当决策引擎:NeoHorse-1-9B 接入工单分流的最小实现 【免费下载链接】NeoHorse-1-9B 项目地址: https://ai.gitcode.com/hf_mirrors/TokenRhythm/NeoHorse-1-9B 工单分流(Ticket Routing)是客服与运维系统里最典型的"文本 →…

📰

遗留代码单元测试实战:从难测到可测的完整路径

接手一套别人写了好几年、注释几乎没有、一上线就没停过修的代码,我第一反应不是打开编辑器开冲,而是先给自己提个问:现在哪些地方是改了必出事的?如果你想给遗留代码补单元测试,却不知道从哪下手,这篇文章…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬