Zed调试宏 C语言错误日志 异常错误调试信息

1、C中的错误码     

        在C语言中通过返回错误码或设置全局的errno值来反馈错误问题。errno.h是一个头文件,它定义了一个全局变量errno,用于在程序中记录和报告错误的原因。这个机制主要用于处理系统调用或标准库函数出错时的错误反馈。当系统调用或库函数遇到错误时,它通常不会直接返回错误信息,而是通过设置errno的值来告知程序具体的错误原因。
        errno在每个程序运行时由操作系统维护。它的值代表了最后一次系统调用或标准库函数失败时的错误类型。每当某个系统调用或库函数返回错误时,errno就会被设置为一个与错误相关的特定值。

错误码errno的值是一个整数,它对应着不同的错误类型,每种错误都有一个对应的宏定义。这些宏通常定义在errno.h文件中,例如:

  • EINVAL:无效参数。
  • ENOMEM:内存不足。
  • EIO:输入输出错误。
  • EBADF:文件描述符无效。
  • EACCESS:没有权限访问文件

        这些宏是常量值,为负值,每个宏的名字对应一个特定的错误情况。 当某个函数调用失败并且设置了errno后,程序可以通过检查errno的值来确定错误的类型。常用的做法是调用perror()或者strerror()来输出错误信息。

  • perror():它会根据errno的值打印出对应的错误消息,并且可以在错误信息前添加一个自定义的描述字符串。例如:
     

    perror("Error opening file");
    

    这行代码会输出类似以下的错误信息:

    Error opening file: No such file or directory
    
  • strerror():它返回一个指向静态字符串的指针,表示与errno值对应的错误信息。例如:

    printf("Error: %s\n", strerror(errno));
    

 2、使用Zed来打印错误信息

         其它语言通过异常来解决这个问题,但是这些问题也会在C中出现(其它语言也一样)。在C中你只能够返回一个值,但是异常是基于栈的返回系统,可以返回任意值。C语言中,尝试在栈上模拟异常非常困难,并且其它库也不会兼容。

        解决方案是,使用一系列“调试宏”,它们在C中实现了基本的调试和错误处理系统。这个系统非常易于理解,兼容于每个库,并且使C代码更加健壮和简洁。

        它通过实现一系列转换来处理错误,任何时候发生了错误,你的函数都会跳到执行清理和返回错误代码的“error:”区域。你可以使用check宏来检查错误代码,打印错误信息,然后跳到清理区域。你也可以使用一系列日志函数来打印出有用的调试信息。

#ifndef __dbg_h__  // 如果没有定义 __dbg_h__,则开始宏定义,防止重复引用
#define __dbg_h__

#include <stdio.h>   // 引入标准I/O库,用于输出日志信息。
#include <errno.h>   // 引入errno.h,用于访问错误代码(errno)。
#include <string.h>  // 引入string.h,用于处理字符串函数(例如strerror())。

// 如果没有定义NDEBUG(即没有禁用调试信息),则定义debug宏。
// debug宏将输出当前文件和行号以及调试信息,方便调试时跟踪。
#ifdef NDEBUG
#define debug(M, ...)  // 如果定义了NDEBUG,则禁用调试输出
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)  
// 否则,输出调试信息,包括文件名、行号。
//##__VA_ARGS__,它告诉预处理器将...所在位置的参数注入到fprintf调用的相应位置
#endif

// clean_errno宏:如果errno为0(表示没有错误),则返回"None";否则,返回通过strerror()获取的错误描述字符串。
#define clean_errno() (errno == 0 ? "None" : strerror(errno))

// log_err宏:输出错误日志,格式为"[ERROR] (文件名:行号:errno描述) 错误信息"。
#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

// log_warn宏:输出警告日志,格式为"[WARN] (文件名:行号:errno描述) 警告信息"。
#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

// log_info宏:输出普通信息日志,格式为"[INFO] (文件名:行号) 信息"。
#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

// check宏:检查条件A是否为真。如果不为真,输出错误信息并跳转到error标签。
// 它帮助简化了条件检查和错误处理。
#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

// sentinel宏:用于关键错误发生时的直接跳转。类似于check宏,但通常用于不应继续执行的情况。
#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

// check_mem宏:用于检查内存分配是否成功。如果分配失败,记录错误信息并跳转到error标签。
#define check_mem(A) check((A), "Out of memory.")

// check_debug宏:类似于check宏,但它会在调试模式下打印调试信息,帮助在调试阶段追踪问题。
#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

#endif  // 结束宏定义

        这段代码的作用是简化C语言项目中的错误处理、调试和日志记录。它通过一系列宏定义提供了易于使用的工具,帮助开发者在程序中更好地管理错误,输出调试信息,以及记录不同级别的日志。每个宏的具体作用如下:

  1. 调试功能 (debug):在调试模式下,通过输出当前文件名和行号以及给定的调试信息,帮助开发者定位和解决问题。NDEBUG宏通常用于控制是否开启调试信息输出,默认情况下在编译时禁用调试输出。

  2. 错误日志功能 (log_err, log_warn, log_info):分别用于输出错误、警告和信息日志。在输出时会附加当前文件名、行号及错误代码errno的描述,方便开发者快速定位问题。

  3. 条件检查 (check, check_mem, check_debug):这些宏用于检查某个条件是否为真,如果条件不满足,则记录错误信息并跳转到error标签。check_mem专门用于检查内存分配是否成功。check_debug则在调试模式下提供额外的信息输出,帮助在调试时更清楚地了解程序的执行状态。

  4. 错误处理 (sentinel)sentinel可以放在函数的任何不应该执行的地方,它会打印错误信息并且跳到error:标签。你可以将它放到if-statements或者switch-statements的不该被执行的分支中,比如default

  5. clean_errno:这个宏用于清理errno的值,如果没有错误,返回"None";如果有错误,返回对应的错误信息。用于获取errno的安全可读的版本

         是的,仅仅22行的代码就可以充当一个错误日志打印的插件,可以完成简化错误处理、日志记录和调试过程,让代码更加清晰和易于维护。并且通过goto error 控制程序的流程,在出现错误时跳转到一个名为 error 的标签。通过使用 goto,程序会跳过剩余的正常流程,直接转到错误处理代码部分。这种做法通常用于清理资源和终止程序执行。

<think>我们正在讨论如何在U-Boot中启用调试日志或调试信息输出。根据用户提供的引用,特别是引用[3]提到了U-Boot下进行gdb调试,其中涉及了调试信息的生成。另外,引用[4]提到了U-Boot的console和printf,这些与输出调试信息相关。 在U-Boot中启用调试信息通常有两种方式: 1. 在编译时通过定义(如DEBUG)来开启特定模块的调试信息。 2. 在运行时通过环境变量和命令控制输出级别。 下面将详细说明这两种方式。 ### 1. 编译时启用调试信息 在U-Boot源码中,很多文件使用`debug()`来输出调试信息,这些在未定义`DEBUG`时为空。因此,要启用某个文件的调试信息,需要在编译时定义`DEBUG`。 **步骤:** - 找到需要调试的源文件,在文件开头(包含头文件之前)添加定义: ```c #define DEBUG ``` 例如,要启用串口驱动的调试信息,可以在`drivers/serial/serial_zynq.c`的开头添加该行。 - 或者,在Makefile中为特定文件添加编译选项: 在U-Boot的Makefile中,可以针对单个文件添加编译标志。例如,在`drivers/serial/Makefile`中: ```makefile CFLAGS_serial_zynq.o += -DDEBUG ``` - 全局启用调试信息(不推荐,因为会产生大量输出): 在配置文件中(如`include/configs/zynq-common.h`)添加: ```c #define DEBUG ``` 或者在编译命令中传递标志: ```bash make CROSS_COMPILE=arm-linux-gnueabihf- EXTRA_CFLAGS=-DDEBUG ``` **注意**:全局启用调试信息可能会导致输出过多,影响启动速度。 ### 2. 运行时控制调试输出 U-Boot提供了`debug`命令来在运行时控制调试信息的输出。该命令可以设置不同子系统的调试级别。 **步骤:** - 确保U-Boot配置了`CONFIG_CMD_DEBUG`和`CONFIG_DEBUG_UART`(如果使用串口输出)。 在配置文件(如`include/configs/zynq-common.h`)中: ```c #define CONFIG_CMD_DEBUG #define CONFIG_DEBUG_UART ``` - 编译并启动U-Boot,在命令行中使用: ```bash => debug [subsystem] [level] ``` 例如,启用串口子系统的调试信息并设置级别为1: ```bash => debug uart 1 ``` 要查看所有可用的子系统和当前级别,可以输入: ```bash => debug ``` 要禁用调试,可以将级别设置为0: ```bash => debug uart 0 ``` ### 3. 使用`CONFIG_LOG`和日志级别(较新版本的U-Boot) 在较新版本的U-Boot(2015年后)中,引入了`CONFIG_LOG`系统,它提供了更灵活的日志控制。 **配置步骤:** - 在配置文件(如`defconfig`)中启用: ```makefile CONFIG_LOG=y CONFIG_LOG_MAX_LEVEL=7 # 最大日志级别,7为调试DEBUG) ``` - 在运行时,可以通过环境变量`log_level`设置日志级别: ```bash => setenv log_level 7 # 设置为DEBUG级别 => saveenv => reset ``` 或者,在启动时通过bootargs传递: ```bash setenv bootargs ... loglevel=7 ``` ### 4. 通过串口输出调试信息 如果调试信息通过串口输出,需要确保: - 串口驱动已正确初始化。 - `CONFIG_DEBUG_UART`已启用,并且在`board_init_f`或`board_init_r`之前调用`debug_uart_init()`(通常在`arch/arm/mach-zynq/spl.c`中处理)。 ### 示例:在Zynq U-Boot中启用调试 以Zynq平台为例,启用串口调试信息: 1. **编译时启用**: 修改`drivers/serial/serial_zynq.c`,在文件开头添加: ```c #define DEBUG ``` 2. **重新编译U-Boot**: ```bash make clean make zynq_zed_defconfig make CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) ``` 3. **运行时控制**: 在U-Boot命令行中: ```bash => debug uart 1 # 启用串口调试信息 => serial # 测试串口,将看到额外调试信息 ``` ### 验证调试信息 启用调试后,执行相关命令(如`mmc info`、`ping`等)时,会在控制台输出额外的调试信息。如果使用串口,确保串口终端已连接并配置正确波特率(通常为115200)。 ### 常见问题 - **无输出**:检查串口连接,确认调试信息是否被重定向到其他设备(如`CONFIG_DEBUG_UART_BASE`设置是否正确)。 - **输出乱码**:检查波特率设置,确保与终端一致。 - **编译错误**:确保在正确位置添加`DEBUG`,避免冲突。 ### 总结 | 方法 | 适用场景 | 操作步骤 | |--------------------|-----------------------------------|---------------------------------------------| | 编译时定义DEBUG | 针对特定文件或模块的详细调试 | 在源文件开头添加`#define DEBUG` | | 运行时debug命令 | 动态调整子系统调试级别 | 使用`debug [subsystem] [level]`命令 | | 启用CONFIG_LOG系统 | 新版本U-Boot的灵活日志控制 | 配置`CONFIG_LOG`并设置`log_level`环境变量 | > **提示**:调试信息可以帮助定位启动过程中的问题,但会增大U-Boot体积并降低启动速度,生产版本应关闭调试[^1][^3]。 §§相关问题§§ 1. 如何在U-Boot中配置串口以输出调试信息? 2. U-Boot的调试命令`debug`支持哪些子系统? 3. 如何减小启用了调试信息的U-Boot镜像大小?</think>### 在 U-Boot 中启用调试日志的步骤 #### 1. **编译时启用调试功能** 在 U-Boot 源码中启用调试定义,这是最核心的配置: ```c /* 在 include/configs/ 目录下的板级配置文件中添加 */ #define DEBUG // 全局调试开关 #define CONFIG_DEBUG // 启用调试基础设施 #define CONFIG_DEBUG_UART 1 // 启用调试串口输出[^4] ``` **关键配置说明:** - `DEBUG`:启用基础调试输出 - `CONFIG_DEBUG`:激活调试子系统 - `CONFIG_DEBUG_UART`:指定调试信息输出到串口(值1通常表示UART0) #### 2. **控制调试级别** 在运行时通过环境变量动态控制日志级别: ```bash # 在 U-Boot 命令行中设置 => setenv log_level 7 # 7=DEBUG级别(0-EMERG, 7-DEBUG) => saveenv # 保存配置 => reset # 重启生效 ``` #### 3. **启用特定模块调试** 在代码中添加模块级调试(需修改源码): ```c /* 在需要调试的源文件开头添加 */ #define DEBUG #include <common.h> // 在函数中使用调试输出 debug("Memory init at 0x%08x\n", base_addr); // 输出调试信息 ``` #### 4. **查看调试信息** - **串口输出**:所有调试信息将输出到配置的串口(默认 115200bps) - **环境变量查看**: ```bash => printenv log_level # 检查当前日志级别 => version # 查看包含DEBUG的编译信息 ``` #### 5. **调试信息示例** 启用后可能看到的输出: ``` U-Boot DEBUG: [mmc.c] SD init start (clock=400000) U-Boot DEBUG: [net/eth.c] PHY addr 0 detected U-Boot DEBUG: [board_f.c] DRAM size=1024MB ``` ### 高级调试技巧 1. **GDB 调试支持**(需额外配置): ```bash # 编译时确保包含调试符号[^3] make CROSS_COMPILE=arm-linux-gnueabihf- EXTRA_CFLAGS="-g -Og" ``` - 使用 `readelf -S u-boot | grep debug` 验证调试段 2. **启动参数调试**: ```bash => setenv bootargs earlyprintk debug # 向内核传递调试参数[^2] => bootm ``` 3. **调试命令**: ```bash => bdinfo # 显示板级信息(含调试数据) => mdio bus # 调试网络PHY ``` ### 注意事项 1. **性能影响**:启用调试会使 U-Boot 体积增加 15-30%,启动时间延长 20% 2. **生产环境**:正式版本应关闭调试(移除 `DEBUG` 定义) 3. **依赖检查**: - 确认 `CONFIG_CMD_LOG` 已启用 - 确保串口驱动正常工作[^4] > **调试等级参考** > | 级别 | 值 | 说明 | > |------|-----|---------------------| > | EMERG| 0 | 系统不可用 | > | ALERT| 1 | 需要立即采取行动 | > | CRIT | 2 | 严重错误 | > | ERR | 3 | 一般错误(默认级别)| > | WARN | 4 | 警告信息 | > | INFO | 6 | 运行状态信息 | > | DEBUG| 7 | 详细调试信息 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值