Skill脚本调试实战指南:从断点设置到异常捕获

5次阅读
没有评论

共计 2727 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

案例引入:静默失败的痛苦

最近接手同事遗留的 PCB 封装生成脚本时,遇到一个诡异现象:脚本能正常执行完毕,但生成的封装焊盘间距总是错误。更头疼的是——没有任何报错信息!这种静默失败 (silent failure) 让我花了整整两天时间,通过几十次 println 语句才定位到问题:一个未初始化的局部变量在条件判断中产生了意外行为。

这个经历让我意识到:掌握系统化的调试方法,比写代码本身更重要。下面分享我在 Skill 脚本调试中总结的实战经验。

调试方法论:print 调试 vs 专业工具

初学者常用 print/println 进行调试,这种方法确实简单直接:

printf("变量 x 当前值:%L" x)  ; 打印变量值
println("执行到第 20 行")     ; 标记执行路径

但存在明显局限性:

  • 需要反复修改代码并重新加载
  • 大量输出导致关键信息被淹没
  • 无法实时观察变量变化过程
  • 对复杂条件分支效率低下

专业调试工具则提供更强大的能力:

  1. 断点调试:冻结程序现场检查状态
  2. 单步执行:逐行跟踪代码流程
  3. 观察点:监控特定变量变化
  4. 堆栈追踪:查看函数调用链

核心调试技术详解

1. 断点调试(Allegro Debugger 实战)

以 Cadence Allegro 为例,内置调试器支持交互式断点调试:

  1. 在代码行号左侧点击设置断点(红色圆点)
  2. 通过 Tools > Debug SKILL 启动调试模式
  3. 触发脚本执行时,程序会在断点处暂停
  4. 使用控制栏进行单步执行(Step Over/Into)

Skill 脚本调试实战指南:从断点设置到异常捕获
图示:①断点标记 ②变量监视窗口 ③单步执行按钮

高级技巧

  • 条件断点 :右键断点设置触发条件(如x > 10 时暂停)
  • 热重载 :修改代码后无需重启,直接Reload 继续调试

2. 结构化日志输出

替代散乱的 print 语句,建议使用标准日志模块:

;; 定义日志级别常量
defvar(log_level 3)  ; 1=ERROR 2=WARN 3=INFO

procedure(logInfo(msg)
  when(log_level >= 3
    printf("[INFO] %s\n" msg)
  )
)

procedure(logError(msg)
  printf("[ERROR] %s\n" msg)
  axlUIConfirm(strcat("ERROR:", msg))  ; 弹窗提示
)

使用方法:

logInfo("开始处理封装数据")
foreach(pad pads
  when(pad->type != "SMD"
    logWarn("发现通孔焊盘:%L" pad->name)
  )
)

优势:

  • 可分级过滤输出
  • 支持输出到文件
  • 统一错误提示格式

3. 异常捕获与堆栈追踪

Skill 支持 try-catch 异常处理机制:

procedure(riskyOperation()
  let((x)
    try(
      x = 1 / 0  ; 故意触发除零错误
      println("这行不会执行")
    )
    catch(err
      printf("捕获异常:%s\n" err->what)
      ;; 打印调用堆栈
      foreach(frame err->stack
        printf("%s:%d %s\n" 
          frame->file frame->line frame->func)
      )
    )
  )
)

典型输出示例:

捕获异常:Division by zero
  debug_demo.il:15 riskyOperation
  main.il:42 wrapperFunc

调试模板代码

以下是一个可直接复用的调试模板:

;; 注册调试命令
axlCmdRegister("test_debug" 'debugDemo)

procedure(debugDemo()
  let((items count (list 1 2 nil 4) 0))
    ;; 设置观察点(Watch Point)printf("初始 count 值:%d\n" count)

    try(
      foreach(item items
        ;; 条件断点示例:当 count= 2 时暂停
        when(count == 2
          printf("断点:count 达到 2\n")
          ;; 此处可在调试器手动检查变量
        )

        unless(item
          error("遇到空值")
        )

        count++
        printf("处理第 %d 项:%L\n" count item)
      )
    )
    catch(err
      printf("异常类型:%s\n" err->type)
      printf("错误信息:%s\n" err->what)

      ;; 访问堆栈帧局部变量(调试器高级功能);; 需要 Allegro 17.4+
      when(boundp('axlDebugGetFrameVars)
        let((vars (axlDebugGetFrameVars 0)))  ; 0= 当前帧
        printf("局部变量:\n")
        foreach(var vars
          printf("%s = %L\n" car(var) cdr(var))
        )
      )
    )
  )
)

避坑指南

环境变量问题

  • 现象:脚本在别人电脑报错 ”File not found”
  • 解决
    ;; 不要硬编码路径
    ;;; 错误示例
    in = "C:/my_scripts/config.txt"
    
    ;;; 正确做法
    config_path = strcat(getShellEnvVar("SCRIPT_DIR") "/config.txt")
    unless(isFile(config_path)
      error("找不到配置文件:%s" config_path)
    )

内存泄漏检测

长期运行的脚本需注意对象释放:

;; 创建测试对象
defstruct(myObj (id 0) (data nil))

let((objs (list))
  for(i 1 1000
    obj = make_myObj(?id i)
    objs = cons(obj objs)  ; 持续累积不释放
  )
  ;; 使用后应该:; objs = nil  ; 释放引用
)

检测方法

  1. 在 Allegro 命令行执行axlMemReport()
  2. 对比脚本执行前后的内存变化

多线程调试

涉及 axlAsyncExecute 的异步代码调试技巧:

;; 主线程设置标记
defvar(async_done nil)

axlAsyncExecute(
  list('println(" 子线程开始 ")'sleep(5)
    'println(" 子线程结束 ")'setq(async_done t)  ; 通知主线程
  )
)

;; 主线程轮询检查
while(!async_done
  println("等待子线程...")
  sleep(1)
)

进阶思考与实践

思考题:如何实现远程调试?

  • 方案 1:通过 Socket 通信转发调试命令
  • 方案 2:使用共享日志文件实时监控

实践任务

  1. 修改示例模板,故意制造数组越界错误
  2. 捕获异常并打印出错位置的周边变量
  3. 扩展日志系统,添加时间戳和线程 ID

调试能力的提升没有捷径,建议大家:

  1. 从简单脚本开始刻意练习断点调试
  2. 养成添加防御性日志的习惯
  3. 复杂问题先用小样本复现
  4. 善用 axlHelp 查看函数文档

遇到特别棘手的问题时,不妨休息几分钟——很多时候灵感就在放松时突然出现。

正文完
 0
评论(没有评论)