第10章 嵌入式linux的调试技术

时间:2021-12-29 18:52:11

一、      防止函数printk降低linux性能:

利用C语言中的编译指令(#if、#else、#endif等)。

现在修改printk_demo驱动代码,通过编译指令定义了一个pr_debug宏,并通过修改编译指令的条件值来控制是否调用printk函数。如下:

# if 1//此处为1,使用printk函数,为0,忽略printk函数

  #define pr_debug(x,…)  do { } while(0)

#endif

除此之外,我们还需要了解两个知识点:1.可变参数的宏:可变参数的宏与固定参数的宏之间的区别:可变参数的宏需要通过_VA_ARGS_宏【不支持可变参数个数为0的情况】获取可变参数的宏的可变参数。定义可变参数宏与定义可变参数函数的方法相同,都使用3个点(…)来表示可变参数,可变参数必须是宏和函数最后的参数。

二、      通过虚拟文件系统(/proc)进行数据交互:

必要性:在linux文件系统中,/proc经常被用来作为内核空间进行数据交互的工具。/proc是虚拟文件系统,也就是说,/proc并不是真正的文件系统,而是内存映射。所有读写/proc的操作军事对内存读写的操作。所以读写/proc文件系统的速度远比读写/dev要快。因此,/proc文件系统也可作为linux驱动与用户空间程序交互的工具。

很多信息就是通过/proc文件系统由内核空间的程序向外届提供的。例如:当前系统内存资源就是通过/proc/meminfo文件读取的,读者可以使用如下命令:查看/proc/meminfo文件的内容:Cat /proc/meminfo.我们可以通过执行free命令看看显示的信息是否和meminfo文件中的部分内容相匹配。

在linux驱动程序中可以使用内核函数在/proc目录中创建和删除虚拟文件,也可以建立和删除虚拟目录。

  1. 1. create_proc_entry 创建proc 文件 
    struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,struct proc_dir_entry *parent);

name: 要创建的文件名称;

mode: 该文件的保护掩码;

parent: 确定文件所在目录,如果置NULL ,则位置为/proc 下。

  1. 2. proc_mkdir 创建目录

/* 该函数在父目录parent 下创建一个目录name* */

struct proc_dir_entry * proc_mkdir (const char *name,struct proc_dir_entry *parent);

@name : 要创建的目录名

@parent : 这个目录的父目录

3.remove_proc_entry 删除文件或目录

/* 这个函数从proc 文件系统中删除一个文件或目录。

* 注意:1 .是通过参数name ,而不是通过创建时返回的指针来删除的。

* 2 .该函数不会递归删除目录下的文件。

* 3 .data 变量保存了分配的内存,要先释放对应内存,再删除该文件。

* */

void remove_proc_entry (const char *name,struct proc_dir_entry *parent);

@name : 要删除的文件或目录名

@parent : 所在的父目录 

 4.create_proc_read_entry 创建只读proc 文件

struct proc_dir_entry * create_proc_read_entry (const char

*name,mode_t mode,struct proc_dir_entry *parent,read_proc_t*

read_proc,void *data);

@name : 要创建的文件名

@mode : 要创建的文件的属性 默认0755

@parent : 这个文件的父目录

@read_proc : 当用户读这个文件时,内核调用的函数

@data : 传给read_proc 的参数

注意:删除虚拟文件目录之前,要先删除目录中的虚拟文件。

执行build.sh脚本文件,会将proc_demo驱动安装在Ubuntu Linux、开发板或Android虚拟机上。然后执行下面的命令查看/proc/proc_demo目录中的内容。

Ls –al /proc/proc_demo/bin2dec

Cat /proc/proc_demo/bin2dec

Cat /proc_demo/readonly

三、      调试工具:

  1. 用gdb调试用户空间程序:

Gdb可以跟踪调试用户空间的程序。

对于一个用于测试的可执行程序(gdb_debug.c),我们可以直接运行build.sh脚本文件,但注意加上命令参数-g,完整的编译命令如下:

#gcc –static –g –o gdb_debug /root/drivers/debug/gdb_debug.c

现在使用下面的命令来调试:

Gdb gdb_debug

Gdb包含的命令:

1>         quit:用于退出gdb调试界面

2>          list:用于列出程序中的代码。有三种调用格式:list:显示上一次调用list命令输出的最后一行后面的10行。 list-: 显示上一次调用list命令输出的第一行前面的10行,第一次调用list命令什么都不会显示。list n:显示第n行附近的10行,一般会显示第n行前面5 行和后面4行,加上第n行,正好是10行。

3>         Break n:将指定行设置为断点,n表示行号

4>         Clear n:清除指定行的断点。

5>         Tbreak n:将指定行设置为断点,断点只能使用一次,使用完成后自动清零。

6>         Cont/continue:跳过当前断点继续执行。[cont:跳过当前断点继续执行;cont n:跳过n次断点继续前行]

7>         Next:继续执行下面的语句,但跳过这程序。等价于step over。[同上有两种格式next 及next n]

8>         Nexti:单步执行语句,和next的区别:它会跟踪到子程序的内部,但不打印出子程序内部的语句。

9>         Print var_name:查看变量值.

  1. 用gdbserver远程调试用户空间程序:

1.与GDB的区别:gdb用于PC上进行测试,而gdbserver测试运行于开发板、手机、Android模拟器上。

2.在开发板上使用gdbserver打开测试程序,然后通过串口、有线、无线网络可以在PC上进行测试。

3.第一步:进入Android模拟器的终端,然后进入data/local目录,并执行如下命令:gdbserver :4321 ./gdb_debug启动gdbserver监听程序。其中4321表示使用本机的4321端口号进行监听。

  第二步:开启另一个Linux终端,将外部访问模拟器的4321端口的数据包转发到Android模拟器内部的4321端口:adb –s emulator-5544 forword tcp:4321[有多个Android设备时要加-s命令行参数指定具体的Android设备]。

第二个方法[映射端口]:1.进入telent:telent localhost 5554 2.映射端口:redir add tcp:4321:4321

第三步:进入gdb控制台:arm-none-linux-guneabi-gdb gdb_debug

第四步:连接Android模拟器:(gdb) target remote localhost:4321

            最后:输入gdb命令进行调试。

        通过IP方式连接开发板上的gdbserver:1.gdbserver localhost:4321 ./gdb_debug  2.在linux终端的gdb控制台链接开发板的gdbsrever:(gdb) target remote 192.168.17.103 ./gdb_debug.

        通过串口方式:对应的应该是:1.gdbserver /dev/s3c2410_serial0 ./gdb_debug  2.(gdb) target remote /dev/ttyUSB0

  1. 用kgdb远程调试内核程序:

Kgdb除了提供类似printk函数的日志输出功能,还允许开发人员直接在PC上通过GDB链接目标设备。

Kgdb包含两个部分:kgdb内核和一套链接接口【目前支持串口tty设备链接和以太网连接】。其中串口连接需要通过内核参数kgdboc指定要链接的串口tty设备:以太网连接通过内核参数kgdboc指定IP和端口号。

要想用kgdb调试内核,首先需要配置linux内核。使用make menuconfig命令进入Linux内核的配置菜单。【Kernel hacking--àKGDB:kernel debugger】

     配置内核参数时,这些参数通知Linux内核要如何进行调试。假设要通过USB转COM口数据线进行调试,需要将kgdboc参数值折为ttyUSB0,传输效率为115200,一般会指定kdbwait。这些参数需要在S3C开发板过程中按回车进入Uboot模式,然后使用setenv命令设置Linux内核的启动参数,然后使用saveenv和rest命令保存和重新启动Linux内核。

在设置完成后,主机就可以使用gdb命令像调试普通嵌入式应用程序一样调试Linux内核,执行的命令如下:

#gdb  ./vmlinux

完成后,使用如下的命令设置传输速率和连接也要调试的Linux内核。

(gdb) set remoteband 115200

(gdb) target remote /dev/ttyUSB0

最后使用各种gdb命令进行Linux内核调试。