S390 调试特性¶
- 文件
arch/s390/kernel/debug.c
arch/s390/include/asm/debug.h
描述:¶
此特性的目标是提供一个内核调试日志 API,在该 API 中,日志记录可以有效地存储在内存中,每个组件(例如设备驱动程序)可以拥有一个单独的调试日志。其中一个目的是在生产系统崩溃后检查调试日志,以便分析崩溃的原因。
如果系统仍在运行,但仅使用 dbf 的子组件出现故障,则可以通过 Linux debugfs 文件系统在活动系统上查看调试日志。
调试特性对于内核和驱动程序开发也可能非常有用。
设计:¶
内核组件(例如设备驱动程序)可以通过函数调用 debug_register()
在调试特性中注册自身。此函数为调用者初始化调试日志。对于每个调试日志,都存在多个调试区域,并且一次只有一个处于活动状态。每个调试区域都由内存中连续的页面组成。调试区域中存储了由事件和异常调用写入的调试条目(日志记录)。
事件调用将指定的调试条目写入活动调试区域,并更新活动区域的日志指针。如果到达活动调试区域的末尾,则会进行环绕(环形缓冲区),并且下一个调试条目将写入活动调试区域的开头。
异常调用将指定的调试条目写入日志,并切换到下一个调试区域。这样做是为了确保在当前区域发生环绕时,描述异常来源的记录不会被覆盖。
调试区域本身也以环形缓冲区的形式排序。如果在最后一个调试区域中引发异常,则以下调试条目将再次写入第一个区域。
事件和异常调用有四个版本:一个用于记录原始数据,一个用于文本,一个用于数字(无符号 int 和 long),一个用于 sprintf 类似的格式化字符串。
每个调试条目都包含以下数据
时间戳
调用任务的 CPU 编号
调试条目的级别 (0...6)
到调用者的返回地址
标志,指示条目是否为异常
可以通过 debugfs 文件系统中的条目在活动系统中检查调试日志。在顶层目录 “s390dbf
” 下,每个注册组件都有一个目录,其名称类似于相应的组件。debugfs 通常应挂载到 /sys/kernel/debug
,因此可以在 /sys/kernel/debug/s390dbf
下访问调试特性。
目录的内容是表示调试日志的不同视图的文件。每个组件都可以通过使用函数 debug_register_view()
注册它们来决定应使用哪些视图。提供了用于十六进制/ASCII 和 sprintf 数据的预定义视图。也可以定义其他视图。只需读取相应的 debugfs 文件即可检查视图的内容。
所有调试日志都有一个实际的调试级别(范围从 0 到 6)。默认级别为 3。事件和异常函数都有一个 level
参数。只有级别低于或等于实际级别的调试条目才会写入日志。这意味着,在写入事件时,高优先级日志条目应具有较低的级别值,而低优先级条目应具有较高的级别值。可以使用 debugfs 文件系统通过将数字字符串“x”写入为每个调试日志提供的 level
debugfs 文件来更改实际的调试级别。可以使用 level
debugfs 文件上的“-”完全关闭调试。
示例
> echo "-" > /sys/kernel/debug/s390dbf/dasd/level
也可以为每个调试日志全局停用调试特性。您可以使用 /proc/sys/s390dbf
中的 2 个 sysctl 参数来更改行为
目前有 2 个可能的触发器,它们会全局停止调试特性。第一种可能性是使用 debug_active
sysctl。如果设置为 1,则调试特性正在运行。如果 debug_active
设置为 0,则调试特性已关闭。
停止调试特性的第二个触发器是内核 oops。这样可以防止调试特性覆盖在 oops 之前发生的调试信息。在发生 oops 后,您可以通过将 1 管道传输到 /proc/sys/s390dbf/debug_active
来重新激活调试特性。但是,不建议在生产环境中使用 oopsed 内核。
如果您想禁止停用调试特性,您可以使用 debug_stoppable
sysctl。如果您将 debug_stoppable
设置为 0,则无法停止调试特性。如果调试特性已停止,它将保持停用状态。
内核接口:¶
-
bool debug_next_entry(file_private_info_t *p_info)¶
转到下一个条目
参数
file_private_info_t *p_info
正在操作的私有信息
描述
将 p_info 中的当前位置设置为下一个条目。如果不存在更多条目,则当前位置将设置为末尾之后的位置,并且返回值指示不存在更多条目。
返回
如果存在更多后续条目,则为 True,否则为 False
-
void debug_to_act_entry(file_private_info_t *p_info)¶
转到当前活动条目
参数
file_private_info_t *p_info
正在操作的私有信息
描述
将 p_info 中的当前位置设置为 p_info->debug_info_snap 的当前活动条目
-
bool debug_prev_entry(file_private_info_t *p_info)¶
转到上一个条目
参数
file_private_info_t *p_info
正在操作的私有信息
描述
将 p_info 中的当前位置设置为上一个条目。如果不存在上一个条目,则当前位置将保留为 DEBUG_PROLOG_ENTRY,并且返回值指示不存在上一个条目。
返回
如果存在更多上一个条目,则为 True,否则为 False
-
bool debug_move_entry(file_private_info_t *p_info, bool reverse)¶
向前或向后移动到下一个条目
参数
file_private_info_t *p_info
正在操作的私有信息
bool reverse
如果为 True,则反向移动到下一个条目,即上一个
描述
将 p_info 中的当前位置设置为下一个(reverse == false)或上一个(reverse == true)条目。
返回
如果该方向上存在更多条目,则为 True,否则为 False。
-
ssize_t debug_dump(debug_info_t *id, struct debug_view *view, char *buf, size_t buf_size, bool reverse)¶
获取调试信息的文本表示形式,或尽可能多的信息
参数
debug_info_t *id
要使用的调试信息
struct debug_view *view
用于转储调试信息的视图
char *buf
写入文本调试数据表示形式的缓冲区
size_t buf_size
缓冲区的大小,包括尾部的“0”字节
bool reverse
从上次写入的条目向后移动
描述
只要需要调试信息的文本表示形式而无需使用 s390dbf 文件,就可以使用此函数。
注意
调用者有责任提供与调试信息数据兼容的视图。
返回
成功时,返回写入缓冲区的字节数,不包括尾部的“0”字节。如果 bug_size == 0,则该函数返回 0。失败时,返回小于 0 的错误代码。
-
debug_info_t *debug_register_mode(const char *name, int pages_per_area, int nr_areas, int buf_size, umode_t mode, uid_t uid, gid_t gid)¶
创建和初始化调试区域。
参数
const char *name
调试日志的名称(例如,用于 debugfs 条目)
int pages_per_area
每个区域将分配的页数
int nr_areas
调试区域的数量
int buf_size
每个调试条目中数据区域的大小
umode_t mode
debugfs 文件的文件模式。例如,S_IRWXUGO
uid_t uid
debugfs 文件的用户 ID。当前仅支持 0。
gid_t gid
debugfs 文件的组 ID。当前仅支持 0。
返回
生成的调试区域的句柄
如果注册失败,则为
NULL
描述
为调试日志分配内存。不得在中断处理程序中调用。
-
debug_info_t *debug_register(const char *name, int pages_per_area, int nr_areas, int buf_size)¶
使用默认文件模式创建和初始化调试区域。
参数
const char *name
调试日志的名称(例如,用于 debugfs 条目)
int pages_per_area
每个区域将分配的页数
int nr_areas
调试区域的数量
int buf_size
每个调试条目中数据区域的大小
返回
生成的调试区域的句柄
如果注册失败,则为
NULL
描述
为调试日志分配内存。debugfs 文件模式访问权限对于用户是读写权限。不得在中断处理程序中调用。
-
void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas)¶
注册静态调试区域
参数
debug_info_t *id
静态调试区域的句柄
int pages_per_area
每个区域的页数
int nr_areas
调试区域的数量
描述
注册使用 DEFINE_STATIC_DEBUG_INFO 定义的 debug_info_t。
注意
- 此函数通过 initcall 自动调用,initcall 由
DEFINE_STATIC_DEBUG_INFO 生成。
-
void debug_unregister(debug_info_t *id)¶
交还调试区域。
参数
debug_info_t *id
调试日志的句柄
返回
无
-
void debug_set_level(debug_info_t *id, int new_level)¶
如果 new_level 有效,则设置新的实际调试级别。
参数
debug_info_t *id
调试日志的句柄
int new_level
新的调试级别
返回
无
-
void debug_stop_all(void)¶
如果允许停止,则停止调试特性。
参数
void
无参数
返回
无
描述
当前用于内核 oops 的情况。
-
void debug_set_critical(void)¶
event/exception 函数尝试锁定而不是自旋。
参数
void
无参数
返回
无
描述
当前用于停止除当前 CPU 之外的所有 CPU 的情况。一旦处于此状态,用于为事件或异常写入调试条目的函数将不再在调试区域锁上自旋,而仅尝试获取它,如果它们没有获得锁,则会失败。
-
int debug_register_view(debug_info_t *id, struct debug_view *view)¶
注册新的调试视图并创建 debugfs 目录条目
参数
debug_info_t *id
调试日志的句柄
struct debug_view *view
指向调试视图结构的指针
返回
0:ok
< 0:错误
-
int debug_unregister_view(debug_info_t *id, struct debug_view *view)¶
取消注册调试视图并删除 debugfs 目录条目
参数
debug_info_t *id
调试日志的句柄
struct debug_view *view
指向调试视图结构的指针
返回
0:ok
< 0:错误
-
bool debug_level_enabled(debug_info_t *id, int level)¶
如果将记录指定级别的调试事件,则返回 true。否则返回 false。
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
返回
如果级别小于或等于当前调试级别,则为
true
。
-
debug_entry_t *debug_event(debug_info_t *id, int level, void *data, int length)¶
将二进制调试条目写入活动调试区域 (如果 level <= 实际调试级别)
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
void *data
指向调试条目数据的指针
int length
数据长度(字节)
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_int_event(debug_info_t *id, int level, unsigned int tag)¶
将无符号整数调试条目写入活动调试区域 (如果 level <= 实际调试级别)
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
unsigned int tag
调试条目的整数值
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_long_event(debug_info_t *id, int level, unsigned long tag)¶
将无符号长整数调试条目写入活动调试区域 (如果 level <= 实际调试级别)
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
unsigned long tag
调试条目的长整数值
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_text_event(debug_info_t *id, int level, const char *txt)¶
将 ASCII 格式的字符串调试条目写入活动调试区域 (如果 level <= 实际调试级别)
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
const char *txt
调试条目的字符串
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_sprintf_event¶
debug_sprintf_event (_id, _level, _fmt, ...)
使用格式字符串和可变参数 (longs) 将调试条目写入活动调试区域 (如果 level $<=$ 实际调试级别)。
参数
_id
调试日志的句柄
_level
调试级别
_fmt
调试条目的格式字符串
...
可变参数的使用方式与
sprintf()
相同
返回
写入的调试条目的地址
出错时返回
NULL
描述
浮点数和 long long 数据类型不能用作可变参数。
-
debug_entry_t *debug_exception(debug_info_t *id, int level, void *data, int length)¶
将二进制调试条目写入活动调试区域 (如果 level <= 实际调试级别) 并切换到下一个调试区域
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
void *data
指向调试条目数据的指针
int length
数据长度(字节)
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_int_exception(debug_info_t *id, int level, unsigned int tag)¶
将无符号整数调试条目写入活动调试区域 (如果 level <= 实际调试级别) 并切换到下一个调试区域
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
unsigned int tag
调试条目的整数值
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_long_exception(debug_info_t *id, int level, unsigned long tag)¶
将长整数调试条目写入活动调试区域 (如果 level <= 实际调试级别) 并切换到下一个调试区域
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
unsigned long tag
调试条目的长整数值
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_entry_t *debug_text_exception(debug_info_t *id, int level, const char *txt)¶
将 ASCII 格式的字符串调试条目写入活动调试区域 (如果 level <= 实际调试级别) 并切换到下一个调试区域
参数
debug_info_t *id
调试日志的句柄
int level
调试级别
const char *txt
调试条目的字符串
返回
写入的调试条目的地址
出错时返回
NULL
-
debug_sprintf_exception¶
debug_sprintf_exception (_id, _level, _fmt, ...)
使用格式字符串和可变参数 (longs) 将调试条目写入活动调试区域 (如果 level <= 实际调试级别) 并切换到下一个调试区域。
参数
_id
调试日志的句柄
_level
调试级别
_fmt
调试条目的格式字符串
...
可变参数的使用方式与
sprintf()
相同
返回
写入的调试条目的地址
出错时返回
NULL
描述
浮点数和 long long 数据类型不能用作可变参数。
-
DEFINE_STATIC_DEBUG_INFO¶
DEFINE_STATIC_DEBUG_INFO (var, name, pages, nr_areas, buf_size, view)
定义静态 debug_info_t
参数
var
debug_info_t 变量的名称
name
调试日志的名称(例如,用于 debugfs 条目)
pages
每个区域的页数
nr_areas
调试区域的数量
buf_size
每个调试条目中数据区域的大小
view
指向调试视图结构的指针
描述
为早期跟踪定义一个静态 debug_info_t。关联的 debugfs 日志会自动使用指定的调试视图注册。
重要提示:此宏的用户不得为此 debug_info_t 调用任何 debug_register/_unregister() 函数!
注意
跟踪将从固定数量的初始页面和区域开始。调试区域将被更改为在 arch_initcall 期间使用指定的数字。
预定义视图:¶
extern struct debug_view debug_hex_ascii_view;
extern struct debug_view debug_sprintf_view;
示例¶
/*
* hex_ascii-view Example
*/
#include <linux/init.h>
#include <asm/debug.h>
static debug_info_t *debug_info;
static int init(void)
{
/* register 4 debug areas with one page each and 4 byte data field */
debug_info = debug_register("test", 1, 4, 4 );
debug_register_view(debug_info, &debug_hex_ascii_view);
debug_text_event(debug_info, 4 , "one ");
debug_int_exception(debug_info, 4, 4711);
debug_event(debug_info, 3, &debug_info, 4);
return 0;
}
static void cleanup(void)
{
debug_unregister(debug_info);
}
module_init(init);
module_exit(cleanup);
/*
* sprintf-view Example
*/
#include <linux/init.h>
#include <asm/debug.h>
static debug_info_t *debug_info;
static int init(void)
{
/* register 4 debug areas with one page each and data field for */
/* format string pointer + 2 varargs (= 3 * sizeof(long)) */
debug_info = debug_register("test", 1, 4, sizeof(long) * 3);
debug_register_view(debug_info, &debug_sprintf_view);
debug_sprintf_event(debug_info, 2 , "first event in %s:%i\n",__FILE__,__LINE__);
debug_sprintf_exception(debug_info, 1, "pointer to debug info: %p\n",&debug_info);
return 0;
}
static void cleanup(void)
{
debug_unregister(debug_info);
}
module_init(init);
module_exit(cleanup);
Debugfs 接口¶
可以通过读取相应的 debugfs 文件来调查调试日志的视图
示例
> ls /sys/kernel/debug/s390dbf/dasd
flush hex_ascii level pages
> cat /sys/kernel/debug/s390dbf/dasd/hex_ascii | sort -k2,2 -s
00 00974733272:680099 2 - 02 0006ad7e 07 ea 4a 90 | ....
00 00974733272:682210 2 - 02 0006ade6 46 52 45 45 | FREE
00 00974733272:682213 2 - 02 0006adf6 07 ea 4a 90 | ....
00 00974733272:682281 1 * 02 0006ab08 41 4c 4c 43 | EXCP
01 00974733272:682284 2 - 02 0006ab16 45 43 4b 44 | ECKD
01 00974733272:682287 2 - 02 0006ab28 00 00 00 04 | ....
01 00974733272:682289 2 - 02 0006ab3e 00 00 00 20 | ...
01 00974733272:682297 2 - 02 0006ad7e 07 ea 4a 90 | ....
01 00974733272:684384 2 - 00 0006ade6 46 52 45 45 | FREE
01 00974733272:684388 2 - 00 0006adf6 07 ea 4a 90 | ....
有关上述输出的说明,请参见有关预定义视图的部分!
更改调试级别¶
示例
> cat /sys/kernel/debug/s390dbf/dasd/level
3
> echo "5" > /sys/kernel/debug/s390dbf/dasd/level
> cat /sys/kernel/debug/s390dbf/dasd/level
5
刷新调试区域¶
可以通过将所需区域的编号 (0...n) 通过管道传递到 debugfs 文件 “flush” 来刷新调试区域。当使用 “-” 时,所有调试区域都将被刷新。
示例
刷新调试区域 0
> echo "0" > /sys/kernel/debug/s390dbf/dasd/flush
刷新所有调试区域
> echo "-" > /sys/kernel/debug/s390dbf/dasd/flush
更改调试区域的大小¶
可以通过将页面数量通过管道传递到 debugfs 文件 “pages” 来更改调试区域的大小。调整大小请求也会刷新调试区域。
示例
为调试功能 “dasd” 的调试区域定义 4 个页面
> echo "4" > /sys/kernel/debug/s390dbf/dasd/pages
停止调试功能¶
示例
检查是否允许停止
> cat /proc/sys/s390dbf/debug_stoppable
停止调试功能
> echo 0 > /proc/sys/s390dbf/debug_active
crash 接口¶
自 v5.1.0 以来的 crash
工具具有内置命令 s390dbf
来显示所有调试日志或将其导出到文件系统。使用此工具,可以在实时系统上和系统崩溃后的内存转储中调查调试日志。
调查原始内存¶
在实时系统上和系统崩溃后调查调试日志的最后一种可能性是在 VM 下或在服务元素中查看原始内存。 可以通过系统映射中的 debug_area_first
符号找到调试日志的锚点。 然后,必须遵循 debug.h 中定义的数据结构的正确指针,并在内存中找到调试区域。 通常,使用调试功能的模块也将具有一个全局变量,该变量带有指向调试日志的指针。 遵循此指针也将可以在内存中找到调试日志。
对于此方法,建议对 debug_register()
中的数据字段长度使用 '16 * x + 4' 字节 (x = 0..n),以便更好地格式化调试条目。
预定义视图¶
有两种预定义的视图:hex_ascii 和 sprintf。hex_ascii 视图以十六进制和 ASCII 格式显示数据字段(例如 45 43 4b 44 | ECKD
)。
sprintf 视图以与 sprintf 函数相同的方式格式化调试条目。 sprintf event/exception 函数将指向格式字符串(大小 = sizeof(long))的指针以及每个可变参数的长整型值写入调试条目。 因此,例如,对于具有格式字符串加上两个可变参数的调试条目,需要在 debug_register()
函数中分配一个 (3 * sizeof(long)) 字节的数据区域。
- 重要提示
在 sprintf event 函数中使用 “%s” 是危险的。 只有在传递字符串的内存在调试功能存在期间可用时,才能在 sprintf event 函数中使用 “%s”。 其背后的原因是,出于性能考虑,只有指向字符串的指针存储在调试功能中。 如果记录的字符串在之后被释放,则在检查调试功能时会发生 OOPS,因为调试功能会访问已释放的内存。
- 注意
如果使用 sprintf 视图,请不要使用 sprintf-event 和 -exception 函数以外的其他 event/exception 函数。
hex_ascii 和 sprintf 视图的格式如下
区域编号
时间戳(格式为自 1970 年 1 月 1 日协调世界时 (UTC) 00:00:00 以来的秒数和微秒数)
调试条目的级别
异常标志 (* = 异常)
调用任务的 CPU 编号
到调用者的返回地址
数据字段
hex_ascii 视图的典型行将如下所示(第一行仅用于解释,在 “cating” 视图时不会显示)
area time level exception cpu caller data (hex + ascii)
--------------------------------------------------------------------------
00 00964419409:440690 1 - 00 88023fe
定义视图¶
视图使用 “debug_view” 结构指定。 定义了用于读取和写入 debugfs 文件的回调函数
struct debug_view {
char name[DEBUG_MAX_PROCF_LEN];
debug_prolog_proc_t* prolog_proc;
debug_header_proc_t* header_proc;
debug_format_proc_t* format_proc;
debug_input_proc_t* input_proc;
void* private_data;
};
其中
typedef int (debug_header_proc_t) (debug_info_t* id,
struct debug_view* view,
int area,
debug_entry_t* entry,
char* out_buf);
typedef int (debug_format_proc_t) (debug_info_t* id,
struct debug_view* view, char* out_buf,
const char* in_buf);
typedef int (debug_prolog_proc_t) (debug_info_t* id,
struct debug_view* view,
char* out_buf);
typedef int (debug_input_proc_t) (debug_info_t* id,
struct debug_view* view,
struct file* file, const char* user_buf,
size_t in_buf_size, loff_t* offset);
“private_data” 成员可用作指向视图特定数据的指针。 它不被调试功能本身使用。
从 debugfs 读取视图时,输出结构如下
"prolog_proc output"
"header_proc output 1" "format_proc output 1"
"header_proc output 2" "format_proc output 2"
"header_proc output 3" "format_proc output 3"
...
从 debugfs 读取视图时,调试功能会调用一次 'prolog_proc' 来写入 prolog。 然后为每个现有的调试条目调用 'header_proc' 和 'format_proc'。
input_proc 可用于实现写入视图时的功能(例如 echo "0" > /sys/kernel/debug/s390dbf/dasd/level
)。
对于 header_proc,可以使用 debug.h 中定义的默认函数 debug_dflt_header_fn()
,该函数产生与预定义视图相同的标头输出。 例如
00 00964419409:440761 2 - 00 88023ec
为了了解如何使用回调函数,请检查默认视图的实现!
示例
#include <asm/debug.h>
#define UNKNOWNSTR "data: %08x"
const char* messages[] =
{"This error...........\n",
"That error...........\n",
"Problem..............\n",
"Something went wrong.\n",
"Everything ok........\n",
NULL
};
static int debug_test_format_fn(
debug_info_t *id, struct debug_view *view,
char *out_buf, const char *in_buf
)
{
int i, rc = 0;
if (id->buf_size >= 4) {
int msg_nr = *((int*)in_buf);
if (msg_nr < sizeof(messages) / sizeof(char*) - 1)
rc += sprintf(out_buf, "%s", messages[msg_nr]);
else
rc += sprintf(out_buf, UNKNOWNSTR, msg_nr);
}
return rc;
}
struct debug_view debug_test_view = {
"myview", /* name of view */
NULL, /* no prolog */
&debug_dflt_header_fn, /* default header for each entry */
&debug_test_format_fn, /* our own format function */
NULL, /* no input function */
NULL /* no private data */
};
测试:¶
debug_info_t *debug_info;
int i;
...
debug_info = debug_register("test", 0, 4, 4);
debug_register_view(debug_info, &debug_test_view);
for (i = 0; i < 10; i ++)
debug_int_event(debug_info, 1, i);
> cat /sys/kernel/debug/s390dbf/test/myview
00 00964419734:611402 1 - 00 88042ca This error...........
00 00964419734:611405 1 - 00 88042ca That error...........
00 00964419734:611408 1 - 00 88042ca Problem..............
00 00964419734:611411 1 - 00 88042ca Something went wrong.
00 00964419734:611414 1 - 00 88042ca Everything ok........
00 00964419734:611417 1 - 00 88042ca data: 00000005
00 00964419734:611419 1 - 00 88042ca data: 00000006
00 00964419734:611422 1 - 00 88042ca data: 00000007
00 00964419734:611425 1 - 00 88042ca data: 00000008
00 00964419734:611428 1 - 00 88042ca data: 00000009