首页
社区
课程
招聘
[原创]使用GDK7调试Linux内核之printk函数
2021-7-5 17:56 6403

[原创]使用GDK7调试Linux内核之printk函数

2021-7-5 17:56
6403

1. 环境搭建

1.1 Ubuntu操作系统的GDK7连接网络的过程记录

1.      在USB接口上插入Mercury的无线网卡。

l  Windows中可直接运行网卡驱动,在等待网卡驱动安装完成以后就可以连接WiFi了。

l  初始的Ubuntu中由于没有Wine,故无法直接运行.exe应用程序;同时通过在终端输入ifconfig命令时提示安装net-tools,于是可以发现Ubuntu是非常“干净”的?

2.      进入设置=>进入WiFi面板=>显示未发现WiFi适配器=>在终端输入lsusb,发现USB无线网卡设备可以被识别出来=>没有安装网卡驱动=>无法连接WiFi =>尝试使用其他方法连接网络来安装网卡驱动。

3.      尝试给GDK7连接网络。

l  让GDK7通过网线与路由器连接,从而连接网络=>实际可行但我这不行=>否掉此方案。

l  手机开启个人热点并开启USB网络共享,然后通过数据线与GDK7连接,从而连接网络=>可以但涉及下载及更新,比较耗费流量=>选择此方案。

4.      通过手机热点及数据线如愿的让Ubuntu连接上了网络。

5.      进入清华大学开源软件镜像站=>点击Ubuntu旁边的问号进入Ubuntu镜像使用帮助页面=>选择合适的Ubuntu版本(GDK7自带的Ubuntu版本默认是18.04.1LTS)=>复制文本内容。

l  或通过下方链接直接进入Ubuntu镜像使用帮助页面。

https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/

6.      打开终端=>输入cd /etc/apt,进入apt文件夹=>输入ls,查看apt文件夹下的文件=>输入sudo gedit sources.list,修改软件源配置文件=>将文件中原本的文本替换成第5步中复制的文本=>保存并退出=>输入sudo apt-get update,更新本地软件源列表=>输入sudo apt-get upgrade更新已安装的软件至新版本(中途需要按Y),软件更新的版本取决于本地软件源的情况。

l  由于更新所以我的Linux内核版本从5.0.0-23升级到了5.3.0-62。

7.      从下方链接进入水星网络的官方网站后,找到合适的驱动下载并安装。

https://www.mercurycom.com.cn/

8.      成功连接WiFi。

1.2 准备符号文件(GDK7端操作)

1.      根据http://advdbg.org/blogs/advdbg_system/articles/7147.aspx(参考文献中的2)下载Linux的符号文件。

l  若出现仓库“……”没有Release文件的情况则在ddebs.list中注释/删除对应的仓库内容。

l  Linux的内核符号文件默认下载在/usr/lib/debug/boot目录下。

l  Linux其他的符号文件默认下载在/usr/lib/debug/modules目录下。

2.      将Linux的内核符号文件复制到调试主机下;文件在调试主机内的位置无要求。

1.3 准备Linux内核源码(GDK7端操作)

1.      打开终端=>输入uname -a,来查看当前系统的Linux内核版本。

2.      下载并安装对应内核版本的Linux内核源代码。

l  选择合适的镜像源,修改软件源配置文件/etc/apt/sources.list并保存。

l  sudo apt-get update // 更新。

l  apt search linux-source // 查找对应内核版本的的Linux内核源代码。

l  sudo apt install linux-source-x.x.x // 安装Linux内核源代码。

l  cd /usr/src/linux-source-x.x.x // 找到Linux内核源代码包linux-source-x.x.x.tar.bz2。

3.      将Linux内核源代码包复制到调试主机下,文件在调试主机内的位置无要求。

2. printk函数的分析

2.1 printk函数的介绍

printk函数是专供内核程序实现打印功能的函数(内核态模式下实现打印功能),显然printk函数只能在内核代码中使用;printk函数主要的作用是将信息记录到日志中及对外打印信息;Linux系统日志架构的说明见图2.1。

图2.1 Linux系统日志架构的说明

2.2 printk函数与printf函数

假如你熟悉C语言那么你一定对printf函数非常的熟悉,printk函数与printf函数不能说一模一样,只能说非常相似=>print kernel(内核)与print format(格式化),两个函数同属于print这一个大家族。

l  printk函数在内核态模式下实现打印功能;printf在用户态下实现打印功能。

l  CPU运行时所处的特权级为内核级0级时,使用者才可调用printk;CPU运行时所处的特权级为应用级3级时,使用者才可调用printf。

l  printk函数可控制打印的信息是否出现在终端上,只有当日志级别小于控制台级别时,printk函数打印的信息才会在终端上显示。

 

print家族的简单说明

1.      print类函数的家族成员:

l  printB/vprintB

l  sprintB/vsprintB

l  fprintB/vfprintB

2.      print类函数的标准格式:AprintB

A:大概可分为空/s/f/v=>

l  空:默认情况;代表向标准化设备(如显示器)输出打印的信息。

l  s:全称为string;代表向字符串缓冲区输出打印的信息。

l  f:全称为file;代表输出打印的信息。

l  v:全称为va_arg;意思是接受va_list参数,将可变参数函数的格式调用变为接受可变参数的固定参数函数的格式调用。

B:k/f=>全称为kernel(内核)/format(格式化)。

2.3 printk函数赏析

1.      printk函数的原型:

asmlinkage __visible int printk(const char *fmt, ...)

{

       printk_func_t vprintk_func;

       va_list args;

       int r;

       va_start(args, fmt);

 

       /*

        * If a caller overrides the per_cpu printk_func, then it needs

        * to disable preemption when calling printk(). Otherwise

        * the printk_func should be set to the default. No need to

        * disable preemption here.

        */

       vprintk_func = this_cpu_read(printk_func);

       r = vprintk_func(fmt, args);

       va_end(args);

 

       return r;

}

 

2.      示例赏析:

vprintk_emit (facility=0, level=-1, dict=0x0, dictlen=0,

fmt=0xc18b0fa2 "\001\066brd: module loaded\n", args=0xe644bee4 "")

l  Facility = 0 代表信息源自内核

l  Level = -1, 是来自printk的调用时,指定的默认日志级别,vprintk_emit函数内部会尝试从fmt参数中提取level值

 

3.      日志级别(共8个级别)=> /include/linux/kernel.h:

#defineKERN_EMERG"<0>"         /*紧急事件消息;系统崩溃之前提示,表示系统不可用*/

#defineKERN_ALERT"<1>"           /*报告消息;表示必须立即采取措施*/

#defineKERN_CRIT"<2>"              /*临界条件;一般涉及严重的硬件或软件操作失败*/

#defineKERN_ERR"<3>"               /*错误条件;驱动程序常用KERN_ERR来报告硬件的错误*/

#defineKERN_WARNING"<4>"    /*警告条件;对可能出现问题的情况进行警告*/

#defineKERN_NOTICE"<5>"        /*正常且重要的条件;常用于与安全相关的消息*/

#defineKERN_INFO"<6>"            /*提示信息;如驱动程序启动时,打印硬件信息*/

#defineKERN_DEBUG"<7>"         /*调试级别的消息*/

 

4.      消息头结构:

struct printk_log {

       u64 ts_nsec;          /*时间戳(纳秒)*/

       u16 len;                /* 全部记录的长度 */

       u16 text_len;          /*文本缓存区的长度*/

       u16 dict_len;          /* 字典缓冲区的长度 */

       u8 facility;             /* 系统日志的来源*/

       u8 flags:5;             /* 内部记录 */

       u8 level:3;             /*系统日志的级别 */

};

 

5.      cat /proc/sys/kernel/printk=>4 4 1 7

l  左数第1个数字4称为控制台日志级别。此数值决定打印到控制台的任何消息的最低优先级;优先级越低,日志级别的数字越高。

l  左数第2个数字4决定了不维持特定日志级别的所有消息的默认日志级别。

l  数字1 决定了整个控制台日志级别中最低日志级别的配置。

l  数字7决定了整个控制台日志级别的默认值。

1)      控制台日志级别,级别更高(0-3)的才显示在控制台.

2)      默认的消息日志级别( KERN_WARNING ),调用printk时没有指定日志级别时会使用这个值

3)      控制台日记级别的允许最小值,dmesg –n 0会失败

4)      默认的控制台日志级别,即第一项的默认值

6.      Facility:

LOG_AUTH             security/authorization    messages    (DEPRECATED    Use

LOG_AUTHPRIV instead)

LOG_AUTHPRIV         security/authorization messages (private)

LOG_CRON            clock daemon (cron and at)

LOG_DAEMON         system daemons without separate facility value

LOG_FTP                ftp daemon

LOG_KERN              kernel messages (these can't be generated from user pro‐cesses)

LOG_LOCAL0 through LOG_LOCAL7

                    reserved for local use

LOG_LPR                line printer subsystem

LOG_MAIL              mail subsystem

LOG_NEWS             USENET news subsystem

LOG_SYSLOG           messages generated internally by syslogd(8)

LOG_USER (default)

                      generic user-level messages

LOG_UUCP             UUCP subsystem

 

7.      改变控制台日志级别的方法:

l  启动时Kernel boot option:loglevel=level

l  运行时Runtime: dmesg -n level

(注意:demsg -n level 改变的是console上的loglevel,dmesg命令仍然会打印出所有级别的系统信息。)

l  运行时Runtime: echo $level > /proc/sys/kernel/printk

l  运行时Runtime:写程序使用syslog系统调用(可以man syslog)

 

#include <unistd.h>

#include <sys/syscall.h>

static inline int syslog(int type, char *bufp, int len)

{

       return syscall(SYS_syslog, type, bufp, len);

}

int main()

{

/* Set the console loglevel to 4 */

syslog(8, 0, 4);

return 0;

}

3. 调试过程

3.1 中断Ubuntu

1.      打开Nano Code并登录=>打开Nano调试并点击左侧栏中的 [糖果](鼠标放上去会提示Nano Debgguer)=>点击 [内核调试] =》点击 [USB3] => 点击[DCI Open] =>点击   [Reconnect] 、[Linux Kernel] 、 [Memory Shadow] => 点击[启动] ;Linux内核调试时应选择的选项详见图3.1。

图3.1 调试Linux内核时应选择的选项

2.      等待 [暂停](鼠标放在上去会提示中断 F6)从灰色变成其他颜色,然后点击;当出现图3.2的画面时代表GDK7已经成功的被断开,此时GDK7仍然处于开机状态,但键盘鼠标失灵。

图3.2 GDK7成功被断开时调试主机端Nano调试的反应

3.      成功中断Ubuntu。

3.2 中断printk

1.      准备在Nano调试的命令行中输入命令;命令行的具体位置见图3.3。

图3.3 Nano调试中命令行的位置

2.      在命令行中输入.sympath+ C:\NanoCode-Symbols\530-62;出现如图3.4所示的画面就代表该命令已经成功被执行。

l  .sympath+ 文件夹的路径 //添加文件夹到符号的搜索路径

图3.4 .sympath+ C:\NanoCode-Symbols\530-62被成功执行的画面

3.      在命令行中输入.srcpath+ C:\NanoCode-Symbols\500-23\linux-source-5.0.0\linux-source-5.0.0;出现如图3.5所示的画面就代表该命令已经成功被执行。

l  .srcpath+ 文件夹//添加文件夹到Linux内核源代码的搜索路径中。

图3.5 .srcpath+ C:\NanoCode-Symbols\500-23\linux-source-5.0.0\linux-source-5.0.0被成功执行的画面

4.      在命令行中输入.reload;出现如图3.6所示的画面就代表该命令已经成功被执行。

l  .reload //重新加载。

图3.6 .reload被成功执行的画面

5.      在命令行中输入lm;出现如图3.7所示的画面就代表该命令已经成功被执行。

l  lm //显示已经加载的模块。

l  可以看见第4行中Linux的内核模块lk已经成功的加载出来,并且可以在后面看到符号文件所在位置。

图3.7 lm被成功执行的画面

6.      在命令行中分别输入x lk!printk和x lk!vprintk_func来查看printk和vprintk_func两函数的内存地址及接收的参数列表;出现如图3.8所示的画面就代表该命令已经成功被执行。

l  x 模块名!函数名//显示指定模块中指定函数的内存地址及接受的参数列表。

l  x lk!vfs*//显示调试符号。

图3.8 x lk!printk和x lk!vprintk_func两条命令成功被执行的画面

7.      在命令行内输入bp ffffffff`9970d0d0来给printk函数设置断点;出现如图3.9所示的画面就代表命令已经成功被执行。

l  bp 内存地址//在该内存地址处设置断点。

l  bl//列出已存在断点的信息。

图3.9 bp ffffffff`9970d0d0成功被执行的画面

8.      恢复GDK7。

9.      在命令行内输入k或k p来查看栈回溯;出现如图3.10所示的画面就代表命令已经成功被执行。

l  k//显示栈回溯

l  k p//显示栈回溯,带格式化参数。

图3.10 k/k p成功被执行的画面

参考文献

1.      https://blog.csdn.net/henly1217/article/details/107662054

2.      http://advdbg.org/blogs/advdbg_system/articles/7147.aspx

3.      https://sysprogs.com/VisualKernel/tutorials/setup/ubuntu/

4.      http://advdbg.org/blogs/advdbg_system/articles/7148.aspx

5.      http://advdbg.org/blogs/advdbg_system/articles/7149.aspx

6.      https://pan.baidu.com/s/1aROIRIKnqyPOKURzF7s2ew(提取码d7v6

7.      https://www.nanocode.cn/software/gdk7/20200613-GDC_4_LINUX.pdf

8.      https://blog.csdn.net/weixin_30654583/article/details/97783341

9.      https://blog.csdn.net/cokewei/article/details/8000101?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242

10.    Linux_Raymond_Printk 张银奎

附录

1.      GDK7的官网链接:https://www.nanocode.cn/#/gdk7/index

2.      NanoCode的官网链接:https://www.nanocode.cn/#/home

其他说明

若您有问题咨询及出现链接失效等其他情况请联系邮箱:birdring_001@outlook.com。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-7-5 18:04 被birdring编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (2)
雪    币: 12083
活跃值: (15459)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 2 2021-8-6 18:12
2
1
如此好的文章为什么连优秀都没有?
雪    币: 2633
活跃值: (7104)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
至尊小仙侠 2021-8-6 19:09
3
0
pureGavin 如此好的文章为什么连优秀都没有?
因为我看不懂
游客
登录 | 注册 方可回帖
返回