AI应用—AI调试实践

张开发
2026/4/16 15:45:06 15 分钟阅读

分享文章

AI应用—AI调试实践
一、起因因为最近要调整相关的算法需要对数据输入和输出进行重新的导入和导出。需求是能够将算法提供的原始数据加载后过算法处理后再输出为原始数据。结果这段代码已经几年不维护了。导致现在更改根本不知道从哪条线路下手比较好。几经反复最后确定还是按现行的正常线路进行会更稳妥。但在反复横跳的过程中已经给自己挖下了几个小坑。二、引入AI为了能够快速的解决问题从而达到算法相关应用的目的。引入了最新的GPT5.4。一开始AI也到到处乱找没有什么头绪。后来逐渐聚焦到了TBB的队列节点流程的属性。而且它还顺手查看了一下代码中存在的十几个Core文件。下面把最新的一个Core被加载后的栈打印出来(gdb) bt #0 0x0000000000481cd6 in std::pairbool, unsigned long MhdSequenceWriter::addImagefloat(float const*, unsigned long, unsigned long, unsigned long, double, double, std::functionvoid (std::shared_ptrunsigned char, unsigned long)) () #1 0x0000000000479158 in DatasCacheOutputDevice::addImage(std::shared_ptrRecordObject const) () #2 0x00000000004779eb in DatasCacheOutputDevice::addData(std::shared_ptrRecordObject const) () #3 0x0000000000477351 in DatasCacheOutputDevice::writeData(std::shared_ptrRecordObject) () #4 0x000000000047abd8 in tbb::detail::d1::function_body_leafstd::shared_ptrRecordObject, tbb::detail::d1::continue_msg, AbstractOutput::AbstractOutput(tbb::detail::d1::graph, std::__cxx11::basic_stringchar, std::char_traitschar, std::allocatorchar const, bool)::{lambda(std::shared_ptrRecordObject const)#2}::operator()(std::shared_ptrRecordObject const) () #5 0x0000000000447c7c in tbb::detail::d1::apply_body_task_bypasstbb::detail::d1::function_input_basestd::shared_ptrRecordObject, tbb::detail::d1::graph_policy_namespace::rejecting, tbb::detail::d1::cache_aligned_allocatorstd::shared_ptrRecordObject , tbb::detail::d1::function_inputstd::shared_ptrRecordObject, tbb::detail::d1::continue_msg, tbb::detail::d1::graph_policy_namespace::rejecting, tbb::detail::d1::cache_aligned_allocatorstd::shared_ptrRecordObject , std::shared_ptrRecordObject ::execute(tbb::detail::d1::execution_data) () #6 0x00007f45518371d3 in tbb::detail::r1::task_dispatcher::local_wait_for_allfalse, tbb::detail::r1::outermost_worker_waiter (this0x7f454390ba00, t0x7f45260f3b00, waiter...) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/task_dispatcher.h:323 #7 0x00007f455181d968 in tbb::detail::r1::task_dispatcher::local_wait_for_alltbb::detail::r1::outermost_worker_waiter (thisoptimized out, toptimized out, waiter...) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/task_dispatcher.h:459 #8 tbb::detail::r1::arena::process (this0x7f454401ba60 _IO_stdfile_2_lock, tls...) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/arena.cpp:218 #9 0x00007f455181d6be in tbb::detail::r1::thread_dispatcher_client::process (thisoptimized out, td...) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/thread_dispatcher_client.h:36 #10 tbb::detail::r1::thread_dispatcher::process (this0x7f454401ba60 _IO_stdfile_2_lock, j...) --Type RET for more, q to quit, c to continue without paging-- at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/thread_dispatcher.cpp:183 #11 0x00007f455181b5c3 in tbb::detail::r1::rml::private_worker::run (this0x7f454401ba60 _IO_stdfile_2_lock) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/private_server.cpp:271 #12 0x00007f455181b546 in tbb::detail::r1::rml::private_worker::thread_routine (arg0x7f454401ba60 _IO_stdfile_2_lock) at /localdisk/ci/runner011/intel-innersource/001/_work/libraries.threading.infrastructure.onetbb-ci/libraries.threading.infrastructure.onetbb-ci/onetbb_source_code/src/tbb/private_server.cpp:221 #13 0x00007f4543e94ac3 in start_thread (argoptimized out) at ./nptl/pthread_create.c:442 #14 0x00007f4543f26a40 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81表面上看应该是调用MhdSequenceWriter::addImage函数后崩溃在此处增加日志也可以确认这个问题。在“std::ofstream refMetaFos m_mhdFile”这行代码崩溃记住它。此时AI进行了下面的一系列操作(gdb) frame 0 #0 0x0000000000481cd6 in std::pairbool, unsigned long MhdSequenceWriter::addImagefloat(float const*, unsigned long, unsigned long, unsigned long, double, double, std::functionvoid (std::shared_ptrunsigned char, unsigned long)) () (gdb) info registers rbx rbp rip rbx 0x0 0 rbp 0x0 0x0 rip 0x481cd6 0x481cd6 std::pairbool, unsigned long MhdSequenceWriter::addImagefloat(float const*, unsigned long, unsigned long, unsigned long, double, double, std::functionvoid (std::shared_ptrunsigned char, unsigned long))182 (gdb) x/i $pc 0x481cd6 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE182: cmpb $0x0,0x81(%rbx) (gdb) disassemble $pc-16,$pc16 Dump of assembler code from 0x481cc6 to 0x481ce6: 0x0000000000481cc6 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE166: test %ebp,%ebp 0x0000000000481cc8 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE168: je 0x481d25 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE261 0x0000000000481cca _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE170: test %r12,%r12 0x0000000000481ccd _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE173: je 0x481d25 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE261 0x0000000000481ccf _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE175: lea 0x98(%rbx),%r14 0x0000000000481cd6 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE182: cmpb $0x0,0x81(%rbx) 0x0000000000481cdd _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE189: jne 0x48221f _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE1535 0x0000000000481ce3 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE195: mov $0x6e1ba0,%edi End of assembler dump. (gdb) info registers rdi rbx rdi 0x7f454401ba60 139935470434912 rbx 0x0在上面的操作中可以发现rbx是作为写入器传入的指针的值再和上面让记住的部分对照。引用空指针的变量不崩溃才会怪。三、分析为了对上面的gdb内容进行说明首先对反汇编命令进行处理disassemble $pc-16,$pc16它的意思是进行反汇编操作显示一定范围内的机器指令。pc寄存器是当前的程序计数器即当前执行到了哪条指令然后崩溃了。所以就从这点开始向前和向后各取16个字节即将前后16字节范围内的汇编指令都打印出来。这样的话就可以把握程序崩溃时对相关数据、资源特别是指针的访问前后是否有问题。此时再配合“info registers”命令来查看相应的寄存器的值就可以大致推定问题的原因。比如前面的汇编代码中有 0x481cd6: cmpb $0x0,0x81(%rbx) # 表示当前正在执行的指令就可以去查看一下rbx寄存器的内容。如果为0,自然比较失败就出问题了。也就是说此时是在用空指针在使用。而下面的指令x/i $pc则可以更简洁的显示出当前执行的哪条指令即上面的cmpb $0x0,0x81(%rbx)而此时再使用寄存器查看命令info registers reg rbx rbp rip info registers rdi rbx可以直接显示rbx的内容发现其确实为0上面的调试信息中有。rdi寄存器在Linux中负责传递函数第一个参数字符串操作的目的地址指示。而rbx为基址寄存器rbp是基址指针寄存器rip是指令指针寄存器用于存储下一条要执行的指令内存地址通过上面的调试命令可以查看在程序崩溃时当前栈帧执行的上下文状态如下一条指令、参数值等。这些寄存器如果保存的指针、参数或临时地址为空或异常时就是引起崩溃的最大可能。比如“0x81(%rbx)”就相当于访问空对象偏移0x81的位置。从空中寻找地址必须是异常的。再举一个例子假如当前执行的汇编命令是“mov (%rdi), %rax”就需要查看rdi的值如果为0,则表示进行了空指针的使用。也就是说先通过bt回溯栈信息再通过这三个命令“x/i $pcdisassemblepc−16,pc-16,pc−16,pc16和info registers rdi rbx… ”就可以基本拿到相关的栈崩溃的上下文进而分析出原因。结合这次的具体情况在frame 0#0帧MhdSequenceWriter::addImage的调用过程中反复对rbx进行操作那么就可以基本判定它是this或者与this强相关的核心对象地址。然后向上看调用栈为DatasCacheOutputDevice.cpp中的m_pWriter-addImage()结合上述的分析就可以基本判定是this指针存储在rbx寄存器中调用是引起了崩溃。那么如果在Release发布版的情况如果没有调试信息怎么办可以用下面的办法进入frmae 1通过其进行反推确认DatasCacheOutputDevice.cpp中的m_pWriter-addImage的指针对象的值是否为0使用命令x/i $pc 并看这附近的汇编disassemblepc−32,pc-32,pc−32,pc32(gdb) frame 1 #1 0x0000000000479158 in DatasCacheOutputDevice::addImage(std::shared_ptrRecordObject const) () (gdb) x/i $pc 0x479158 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5448: mov %eax,%r14d (gdb) disassemble $pc-32,$pc32 Dump of assembler code from 0x479138 to 0x479178: 0x0000000000479138 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5416: fimull -0xf(%rcx,%rcx,4) 0x000000000047913c _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5420: mov 0xe8(%rsp),%r8 0x0000000000479144 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5428: vmovsd 0x28(%rsp),%xmm0 0x000000000047914a _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5434: vmovsd 0xf0(%rsp),%xmm1 0x0000000000479153 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5443: call 0x481c20 _ZN4ivus17MhdSequenceWriter8addImageIfEESt4pairIbmEPKT_mmmddSt8functionIFvSt10shared_ptrIhEmEE 0x0000000000479158 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5448: mov %eax,%r14d 0x000000000047915b _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5451: mov %rdx,%rbx 0x000000000047915e _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5454: mov 0xc0(%rsp),%rax 0x0000000000479166 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5462: test %rax,%rax 0x0000000000479169 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5465: movabs $0x100000001,%r12 0x0000000000479173 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5475: je 0x479187 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5495 0x0000000000479175 _ZN4ivus22DatasCacheOutputDevice8addImageESt10shared_ptrIKNS_12RecordObjectEE5477: lea 0xb0(%rsp),%rdi关注move特别是带MhdSequenceWriter::addImage()(注意C改名机制)。这里的汇编的意思是从DatasCacheOutputDevice对象中取成员而rdi寄存器往往是第一个参数即其自身的this指针。如果在上述的范围无法找到相关的rdi的来源可以进一步扩大范围或者干脆使用命令objdump -d --demangle --start-address0x478fc0 --stop-address0x479140 build/bin/demoTest扩大相关的查找范围总之紧盯住rdiLinux环境下就可以了。可以使用类似x/gx $rbx偏移的命令来定位具体的指针的值通过上面的分析可以知道一般来说在frame 0里看的是崩溃时MhdSequenceWriter内部寄存器。而在 frame 1里看的是“调用m_pWriter-addImage()之前调用者是怎么把对象指针传进去的”成员函数调用时this通常会放进rdi所以只要你在frame 1找到call addImage前面那条“把某个成员搬进rdi”的指令就能反推出m_pWriter。四、存储中断的问题回到TBB流程的问题当初问题怀疑存储的导出有问题但这个导出在实践中已经稳定的运行了几年。后来又怀疑修改代码导致的流程问题。直到AI反复说TBB的算法节点创建使用的是默认的tbb::flow::queueing和tbb::flow::rejecting两种机制它们靠传入的配置参数是否为真来确定。实际上工程上使用的是false即tbb::flow::rejecting。换句话说如果节点的处理速度跟不上就会直接扔掉相关的后续数据。就会造成存储无法进行的假象日志只打印一次存储但数据读取却是多次。这也验证了如果不过算法节点则可以顺利打印相关的日志。而且增加了图流程的等待后也可以顺利保存数据的原因。五、解决方式而在这次的数据导入中数据量非常少读取速度几乎可以忽略然后在内存中用定时器进行循环。喂入算法节点的数据比其处理速度的能力大很多所以出现这种情况是正常的。由于目的是为了数据结果所以最终只是在读取节点中增加了图形运算流程的等待理论上直接延时也可以。而崩溃的问题其实就是前面提到的引入的坑在使用另外一条路径时注释掉了一个关键的判断参数导致在数据流进入存储节点后其写入器尚未准备好。此时既可以增加写入器判断也可以增加流程是否启动的判断来决定是否进行写入器操作。为了保持一致仍然采用了后者。六、总结从这次调试结果来看还是比较满意的。AI虽然没给出最终的解决方案但给出了提示的方向和解决问题的思路。这样就可以迅速的朝着正确的方向推理大大减少了相关问题定位所花费的的时间。希望大家能多多引入AI确实是不错的助手。

更多文章