从串口打印到远程调试:i.MX6嵌入式Linux开发实战

发布时间:2026/10/5 1:09:17
从串口打印到远程调试:i.MX6嵌入式Linux开发实战 做嵌入式Linux开发尤其手头有一块i.MX6开发板的时候十有八九都是从串口打印起步的代码里塞满printf编译烧录开机抓日志然后对着串口终端一条一条地看输出。碰上简单的逻辑问题这个办法确实能用但一旦程序跑起来之后出现诡异崩溃、死锁或者某个变量值不符合预期串口打印的短板就立马暴露了要么日志不够细要么为了加日志得反复改代码重新编译效率低得让人抓狂。我大概在两年前彻底切换到Qt Creator远程调试之后这个问题的解决方式才真正变舒服。这篇文章就把我自己在i.MX6上从串口打印转到Qt Creator远程调试的完整过程写下来重点说清楚gdbserver怎么配、Qt Creator里哪些设置容易踩坑以及整套流程实际用起来是什么体验。这个方案适合正在做i.MX6、i.MX8或者其他ARM Cortex-A系列开发板应用层开发的工程师尤其是项目里用Qt写界面或者写业务逻辑、同时又为bug定位头疼的人。全文我会按照为什么换、板子端怎么准备、Qt Creator怎么配、真正调试起来怎么操作、以及排坑经验这个顺序来写所有步骤和配置我都按我实际验证过的方式来描述保证可以直接照抄。1. 为什么必须放弃串口打印调试1.1 串口打印的三大痛点先说串口打印最让人难受的三个地方基本每个从单片机转过来的朋友都深有体会。第一是信息严重不完整。调试并发问题时你根本不知道打印日志那一刻各个线程的状态调试界面问题时你打印一个坐标、一个颜色值打印一百次也还原不出画面的渲染过程。关键变量在某个瞬间变了但你探头去看的时候已经晚了它不像调试器那样能随时冻结现场。第二是调试循环太快反而拖慢节奏。一次标准流程是改代码加printf、重新编译、把新的可执行文件传到开发板、重启程序、观察串口输出、再把printf删掉。这个小循环看着没什么但一天重复几十次时间都在等待和重复操作中消耗掉了。特别是程序要跑几分钟才触发问题的场景串口打印基本让人崩溃。第三是性能和时序干扰。串口打印本身是阻塞式IOprintf调用会打断实时逻辑代码里日志一多程序卡顿、现象变得不真实甚至bug直接被日志掩盖掉。曾经遇到过一个偶发崩溃加打印之后稳定复现不了去掉打印又出来了后来用远程调试器直接挂上去马上定位到是一块内存被越界写坏了这类问题靠串口打印真是神仙难查。1.2 远程调试的核心机制gdbserver和调试器怎么分工远程调试解决上述痛点的核心思路是把程序的执行端和调试指令端分开。在开发板上跑一个轻量级程序叫gdbserver它负责加载并运行你的应用程序同时通过TCP网络监听调试指令在宿主机上Qt Creator里集成的GDB负责发出指令例如设置断点、读取变量、单步执行。两边之间走的是一套标准协议GDB Remote Serial Protocol。简单类比就是gdbserver是被调试程序的监控摄像头和对讲机GDB是工程师手里的遥控器和监视屏两边通过网络连通后你能像调试本地程序一样查看开发板上程序的实时状态。这里有一个关键认知程序本身并没有跑在宿主机上它依然跑在开发板上。所以CPU、外设、文件系统都是开发板上的真实环境调试器只是远程操控——这对嵌入式调试特别重要因为很多问题跟目标环境的硬件、驱动和库版本强相关只有程序真实运行在板子上调试结果才可信。1.3 本方案的整体架构与适用条件整套调试架构分三层开发板层i.MX6开发板运行Linux系统系统里有gdbserver和你的应用程序以及它依赖的共享库。网络层宿主机和目标板通过以太网直连或者经过路由器/交换机组成局域网互通通信端口默认是2345也可以自定义。宿主机层安装Qt Creator配置交叉编译工具链、GDB和远程设备信息开发、编译、部署、调试都在这里操作。这套方案不是只能用来调Qt程序纯C/C的命令行程序也完全可以。只是标题里强调Qt Creator所以本文以Qt工程为例来讲非Qt工程在配置上反而更简单不需要配置Qt版本只要编译器、调试器和设备信息配对即可。2. 开发板端准备把gdbserver跑起来2.1 确认目标板架构与根文件系统类型动手之前先要在开发板上确认两件事CPU架构和根文件系统的类型。在板子终端执行uname -a uname -mi.MX6一般是ARMv7架构uname -m输出通常为armv7l。这一步决定了你下载什么架构的gdbserver以及交叉编译时用哪个--host参数。再看文件系统是哪种。多数i.MX6开发板的出厂Linux是Yocto、Buildroot或者厂商基于Ubuntu/Debian定制来的。判断方法很简单cat /etc/os-release如果出现Ubuntu或者Debian的字样而且板子上有apt命令恭喜直接apt-get install gdbserver就能搞定省去编译。但Yocto和Buildroot精简系统没有包管理器就需要自己交叉编译一个gdbserver丢进去这也是本文重点讲的路径。2.2 交叉编译gdbserver的完整命令我自己最常用的方式是从gdb官方源码编译gdbserver因为gdb和gdbserver最好保持大版本一致避免协议兼容性带来的一些怪问题。第一步在宿主机上下载gdb源码我习惯用12.1这个版本稳定且和大多Qt Creator自带的gdb版本兼容wget https://ftp.gnu.org/gnu/gdb/gdb-12.1.tar.xz tar xf gdb-12.1.tar.xz cd gdb-12.1第二步新建一个build目录进行交叉编译。假设你的交叉编译器前缀是arm-linux-gnueabihf-那么配置命令如下mkdir build-gdbserver cd build-gdbserver ../configure --hostarm-linux-gnueabihf \ --targetarm-linux-gnueabihf \ --prefix/opt/gdbserver-arm \ --disable-gdb \ --enable-gdbserver \ --disable-werror make -j$(nproc) make install这里解释一下几个参数的含义。--host告诉configure脚本编译出来的gdbserver运行在ARM处理器上所以需要用arm-linux-gnueabihf-gcc来编译--target表示它调试的目标也是ARM--disable-gdb很重要因为我们只需要gdbserver这个瘦客户端不需要完整gdb--disable-werror是为了防止某些编译器警告被当成错误。编译成功后在/opt/gdbserver-arm/bin/下能看到gdbserver这个文件用file命令检查一下file /opt/gdbserver-arm/bin/gdbserver输出里应该包含ARM、EABI5之类的字样说明交叉编译成功。有一点很贴心gdbserver是静态链接附近的产物理论上你可以尝试--static静态编译这样丢到任何同架构的板子都能跑省去依赖库的麻烦。我实际测试下来动态链接的gdbserver只要和板子上的libc版本兼容就没问题静态更省心但体积大一点不差这几十MB就无所谓。2.3 用Buildroot或Yocto一步到位如果你的文件系统是Buildroot构建的其实不用手动交叉编译gdbserver在Buildroot的配置菜单里直接勾上相关选项即可make menuconfig进入Target packages→Debugging→gdbserver勾选后重新编译文件系统工具链会自动把gdbserver打进去。Yocto里则是添加gdb相关的IMAGE_FEATURE比如IMAGE_FEATURES dbg-pkgs tools-debug或者在local.conf里EXTRA_IMAGE_FEATURES debug-tweaks tools-debug这种方式最省事版本与系统的glibc完全匹配不太会遇到动态链接的坑。不过有些成熟的商业开发板不轻易让你重构文件系统所以手动交叉编译gdbserver依旧是必须掌握的技能。2.4 上传到开发板并验证运行编译好的gdbserver可以通过NFS、U盘或者scp上传到开发板。我通常把它放到/usr/bin/下scp gdbserver root192.168.1.100:/usr/bin/登录开发板chmod x /usr/bin/gdbserver gdbserver --version看到版本信息就说明基本环境OK。接下来顺手验证一下网络是否通畅在宿主机上ping 192.168.1.100这里建议开发板固定IP避免后面调试时IP变了导致连接失败。我习惯把板子设为192.168.1.100宿主机用192.168.1.10两个地址在同一网段即可板上防火墙如果开着先放行2345端口或者干脆关掉防火墙。很多临时调试环境都是直连网线没有防火墙问题但如果板子是连着公司办公网络的这一步不能省。3. Qt Creator侧配置一套完整的远程调试链路3.1 几个容易搞混的概念Kit、Compiler、Debugger、Device和SysrootQt Creator里的远程调试配置难点不在操作复杂而是几个概念之间的关系容易被绕晕。我用一句话说清楚Kit是一套组合方案它把编译器、调试器、Qt版本、设备、Sysroot绑定在一起Device描述的是开发板包括IP、用户名、密码和部署方式Sysroot则是开发板根文件系统在宿主机上的一个镜像目录调试时GDB靠它来解析目标板上的共享库符号。打个比方Kit是派遣单Sysroot是地图Device是送货地址Debugger是指挥官而gdbserver是前线执行员。缺了任何一个远程调试链路都走不通但最容易被忽略的恰恰是Sysroot。3.2 添加交叉编译器与GDB调试器先准备工具链。如果电脑上还没有交叉编译工具链先装好。我以Linaro的arm-linux-gnueabihf-为例一般安装在/usr/bin/或者/opt/目录。打开Qt Creator进入工具→选项→Kits→编译器标签页点击添加 → GCC → C然后选择arm-linux-gnueabihf-gcc的路径。同样方式添加C编译器选择g。切到调试器标签页点击添加 → 选择GDB路径也就是arm-linux-gnueabihf-gdb。注意这个GDB是宿主机上运行的ARM版本调试器它的作用就是和板子上的gdbserver通信千万别拿x86版本的gdb来用否则协议解析会是错的而且会报目标架构不匹配。3.3 添加远程设备配置IP、用户名与部署方式进入设备标签页点添加 → Generic Linux Device填上开发板IP192.168.1.100、SSH端口默认22、用户名root和密码。这里Qt Creator其实是在用SSH底座来完成文件部署和命令执行所以板子上要有sshd服务在跑。我踩过第一个坑就是有些精简系统默认没装sshd导致Qt Creator测试连接永远失败。验证方法很简单在宿主机上先手动ssh root192.168.1.100能连上才算OK。连不上的话先在板上把sshd装起来。填完信息后点测试连接Qt Creator会尝试SSH登录并在板子上执行命令看到连接成功的提示就说明设备配置没问题。3.4 创建Kit并设置Sysroot回到Kits标签页点击添加然后手动配对设备类型选Generic Linux Device设备选刚才添加的那台。编译器分别选择ARM交叉C/C编译器。调试器选择ARM GDB。Qt版本选择目标板对应的Qt版本。如果你的Qt库也是交叉编译出来的在Qt Versions标签页先注册qmake路径。如果没有特别要求Qt Creator里也可以不选Qt Version直接用CMake构建但调试Qt程序时建议选上能识别Qt类型信息。最关键的一步是Sysroot。如果Qt Creator提示是新版界面在Kit里找到Sysroot项选择开发板根文件系统的镜像目录。这个目录可以是直接用rsync同步来的rootfs——例如rsync -avz root192.168.1.100:/ --exclude/proc --exclude/sys --exclude/dev /opt/imx6-rootfs/也可以从开发板厂商提供的SDK包里解压出的rootfs。设置好Sysroot后GDB才能正确找到板子上的ld-linux-armhf.so.3、libc.so.6、libQt5Core.so.5等库的符号断点和调用栈才能显示为可读的函数名而不是一串地址。创建完成后Kit列表里会多出一个类似i.MX6 Qt远程调试的条目右侧如果有黄色警告提示先按提示一一补齐。3.5 在工程里配置部署、运行参数与工作目录Kit配置好之后还需要对具体的工程做部署与运行设置。打开工程后左侧选择项目模式在Build Settings里确认编译器已经切换成ARM交叉编译然后在Run Settings里做几件事第一设置部署方式。默认的部署步骤通常会通过SSH把本地构建目录里的可执行文件上传到开发板的某个路径比如/home/root/app。如果你用CMake的install规则也可以让Qt Creator调用make install之后再把文件传过去。第二设置运行配置。在运行一栏里勾选在设备上运行然后填入可执行文件在开发板上的路径、工作目录和命令行参数。工作目录尤其重要比如程序需要读取当前目录下的配置文件或者需要创建日志文件不设置好工作目录就会出现文件找不到的怪问题。第三环境变量。如果程序依赖额外的共享库比如你的Qt库在/opt/qt5/lib下需要在环境变量里加LD_LIBRARY_PATH/opt/qt5/lib:$LD_LIBRARY_PATH否则程序在开发板上启动时会直接报error while loading shared libraries。4. 真正调试从启动gdbserver到断点命中4.1 Qt Creator全自动模式点一下就能断下来全部配置完成后调试体验会变得非常顺畅这也是我推荐这种方式的最大理由。点击Qt Creator左下角的调试按钮它会自动完成下面这些事在宿主机上编译工程ARM交叉编译。通过SSH把可执行文件上传到开发板指定路径。通过SSH在开发板上启动gdbserver并把程序加载进去。在宿主机启动arm-linux-gnueabihf-gdb连接开发板上的gdbserver监听端口。连接建立后自动加载符号表停在main入口处。当你看到编辑器里的黄色小箭头指向main函数时就意味着远程调试链路已经通了。这时候在Qt Creator的源码窗口里点击代码行号就能设置断点按F5让程序运行程序执行到断点自动停住F10单步跳过F11单步进入把鼠标悬停在变量上就能看到实时数值。这一套操作下来跟调试本地程序几乎没有差别。我现在的开发习惯是在关键逻辑入口设断点在疑点变量上选中右键添加表达式监视然后直接观察变量在每一步迭代中的变化定位问题效率远比加printf高得多。4.2 手动启动gdbserver作为兜底方案虽然Qt Creator的全自动模式很方便但有些场景它不一定好用。比如程序是脚本或者服务端启动的再比如你想调试程序启动过程中很早的一个阶段Qt Creator的自动部署流程就不够灵活。这时可以手动启动gdbserver在开发板的程序所在目录执行gdbserver 0.0.0.0:2345 ./myapp arg1 arg20.0.0.0:2345表示监听所有网卡的2345端口。然后在Qt Creator的菜单中选择开始调试 → 附加到远程调试...填入开发板IP和端口号一样能连上。手动模式的优点是可以灵活指定程序启动参数、环境变量甚至在gdbserver启动前先用gdb设置set solib-search-path等参数适合排查一些特殊启动流程问题。我自己的经验是Qt Creator自动模式适合日常开发手动模式适合排查疑难杂症。两者搭配着用效率最高。4.3 断点、单步与变量监视的几个实用技巧远程调试的常用操作本地调试几乎都支持但有几个细节值得强调一下在断点命中之后看调用栈是定位问题非常重要的动作。Qt Creator左下角会显示调用栈窗口每一帧对应一个函数调用点击任意一帧就能跳到对应源码行同时查看该函数内的局部变量。如果你发现调用栈中某些帧只有地址、没有函数名多半是Sysroot配置不对或者编译时没加-g选项。查看变量除了悬停鼠标更推荐用表达式求值窗口。比如你输入*(QListQString*)list这种复杂类型表达式Qt Creator结合Qt类型的调试助手能直接展开容器的元素内容。这个功能在排查Qt容器越界、空指针问题时极为好用。还有一点在开发板上调试时程序的stdout输出有时不会完全同步到Qt Creator的应用程序输出窗口如果看不到打印别着急确认一下运行环境里是否配置了标准输出重定向或者使用板子上的串口后台同时观察。4.4 调试Core Dump的延伸玩法远程调试不仅能调试正在运行的程序也可以调试开发板上已经崩溃留下的core dump文件。板子上的/var/crash或者程序所在目录下如果生成了core文件在宿主机上启动GDB加载可执行文件和core文件然后指向开发板的Sysroot同样可以获得完整的崩溃调用栈。具体来说Qt Creator里点开始调试 → 打开Core文件选择本地的可执行文件和core文件再把GDB的Sysroot设置好断点和变量窗口就都能用了。没有core文件的话给板子的内核设置ulimit -c unlimited再复现一次崩溃过程就能在板子上捕获core。这招在排查偶发抖动崩溃这种问题上特别管用省去了一次次远程复现的麻烦。4.5 提到的QML调试既然用了Qt Creator很多人也会写QML界面。QML的调试机制和C调试不太一样它需要Qt Quick调试器在QML运行时注入调试接口。Qt Creator针对远程设备同样支持QML调试前提是编译Qt库时开启了QML调试支持并且在工程构建设置里勾选启用QML调试。开启QML调试后你在Qt Creator里可以对QML文件里的属性绑定、JavaScript函数设置断点也可以实时查看Item的属性值这在排查界面布局错乱、信号连接错误的时候效率很高。不过我建议在调试QML之前先把C部分的远程调试跑通因为QML调试还依赖C侧的正常连接链路是一样的。5. 常见问题排查与经验技巧5.1 连接失败或拒绝连接的排查顺序远程调试遇到最多的问题就是连接失败。每次遇到这种情况我按固定顺序排查先看网络通不通。宿主机直接ping 开发板IP通了再继续。接着验证端口监听情况在开发板上执行netstat -tlnp | grep 2345正常能看到gdbserver监听在2345端口。如果没看到说明gdbserver根本没起来或者起来了又崩了回到板子终端手动跑一下gdbserver看报错信息。接下来确认防火墙。开发板如果是Ubuntu文件系统用iptables -L有规则的话先临时放行2345iptables -I INPUT -p tcp --dport 2345 -j ACCEPT最后看Qt Creator本身的错误提示。连接失败时它一般会说Connection refused还是Connection timed outrefused多半服务端没起来或者端口不对timed out多半网络不通。这样一圈排查下来80%的连接问题都能定位。5.2 断点不生效、程序乱跳行的真正原因断点打上了程序也跑起来了但就是不命中或者命中了跳的行对不上源码——这基本都是编译优化导致的。嵌入式开发经常默认用-O2优化编译器会把变量优化到寄存器里、把循环展开、把函数内联导致断点位置和源码行无法一一对应。解决办法是在构建工程的Debug配置里把编译优化级别降到-O0或者-Og并且加-g生成调试信息。在Qt Creator的Build Settings里切换到Debug构建或者手动在CMake/qmake配置里指定CMAKE_BUILD_TYPEDebug。另外一个容易被忽略的坑是我见过有人把编译器和调试器版本搞混编应用用的是ARM编译器调试器却配成了x86版gdb导致GDB解析调试信息时报错或根本看不到源码。这种情况一定要在Kit里仔细核对调试器路径确实是arm-linux-gnueabihf-gdb。5.3 调试器与gdbserver版本差异的坑GDB和gdbserver的版本太悬殊可能表现出各种奇怪症状比如连上了但一设断点就卡死或者继续运行时报Remote g packet reply is too long这类错误。我踩过最狠的一次是gdb 7.6配gdbserver 9.2直接在连接阶段协议就崩了。所以规范做法是尽量保持宿主机的arm-linux-gnueabihf-gdb与板子的gdbserver版本一致尤其是大版本尽量不要差太多。如果板子上的gdbserver是Buildroot自带的那就用Buildroot里配套的gdb如果是自己编译的就指定同一个gdb源码版本编译两边工具。这样可以最大程度避免一些非常隐蔽的协议兼容性问题。5.4 几个让远程调试更好用的经验最后分享几个我私藏的小经验第一调试时如果把开发板的网络设为DHCPIP地址每次可能变Qt Creator里配置好的设备地址就会失效。所以要么板子固定IP要么路由器上给板子做MAC地址绑定实在不行就每次开机后手动改一下Device的IP。第二如果程序需要以root运行但SSH用户不是root可以在运行配置环境变量里加SHELL/bin/sh或者直接用root用户登录嵌入式开发板上跑调试服务用root最省心。第三不要轻视串口打印的价值。远程调试是主力但串口打印依然是很好的辅助工具尤其是调试内核驱动、uboot、或者系统早期的启动流程时串口几乎是唯一手段。把它当成最后一道防线而不是唯一的调试工具心态就平衡了。第四程序跑起来之后调试会话会占用目标板的资源如果板子内存紧张被调试程序体积又大调试启动阶段会卡一些这时候可以试试把程序strip之后再传上去——注意要保留调试符号文件用于宿主机而板子上放strip过的可执行文件。这个组合能显著减少传输和加载时间同时调试信息一点不丢至少我自己是这么干的。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询