14

我正在构建的内核模块中的一些结构有一个小问题,所以我认为如果有一种简单的方法可以打印出结构及其值会很好 - 下面是我的意思的一个小的用户空间示例.

假设我们有如下简单的 C 示例(以 bash 命令的形式给出):

FN=mtest

cat > $FN.c <<EOF
#include <stdio.h> //printf
#include <stdlib.h> //calloc

struct person
{
 int age; 
 int height; 
};

static struct person *johndoe;

main ()
{

 johndoe = (struct person *)calloc(1, sizeof(struct person));
 johndoe->age = 6; 

 asm("int3"); //breakpoint for gdb

 printf("Hello World - age: %d\n", johndoe->age);

 free(johndoe);
}
EOF

gcc -g -O0 $FN.c -o $FN

# just a run command for gdb
cat > ./gdbcmds <<EOF
run
EOF

gdb --command=./gdbcmds ./$FN 

 

如果我们运行这个例子,程序会编译,gdb 会运行它,并在断点处自动停止。在这里,我们可以执行以下操作:

Program received signal SIGTRAP, Trace/breakpoint trap.
main () at mtest.c:20
20  printf("Hello World - age: %d\n", johndoe->age);
(gdb) p johndoe
$1 = (struct person *) 0x804b008
(gdb) p (struct person)*0x804b008
$2 = {age = 6, height = 0}
(gdb) c
Continuing.
Hello World - age: 6

Program exited with code 0300.
(gdb) q

 

如图所示,在 gdb 中,我们可以将结构指针的值打印(转储?)johndoe{age = 6, height = 0}... 我也想这样做,但直接来自 C 程序;如下例所示:

#include <stdio.h> //printf
#include <stdlib.h> //calloc
#include <whatever.h> //for imaginary printout_struct

struct person
{
 int age; 
 int height; 
};

static struct person *johndoe;
static char report[255]; 

main ()
{

 johndoe = (struct person *)calloc(1, sizeof(struct person));
 johndoe->age = 6; 

 printout_struct(johndoe, report); //imaginary command

 printf("Hello World - age: %d\nreport: %s", johndoe->age, report);

 free(johndoe);
}

 

这将导致如下输出:

Hello World - age: 6
$2 = {age = 6, height = 0}

 

所以我的问题是 - 是否存在这样的想象功能printout_struct- 或者是否有另一种方法可以使这样的打印输出成为可能?

提前感谢您的帮助,
干杯!

4

6 回答 6

17

只是想说-感谢您提供的所有出色且令人难以置信的快速答案,帮助我理解了这个问题(为什么 C 中没有这样的“本机”函数)!

很抱歉回答我自己的问题 - 这样做,以免混淆原始帖子,并能够格式化代码

在进一步寻找时,我设法找到:

这说明了使用进程本身的 pid 调用的技巧gdb,因此我修改了dumpstack在那里找到的函数,以获得以下代码:

FN=mtest

cat > $FN.c <<EOF
#include <stdio.h> //printf
#include <stdlib.h> //calloc, system

extern const char *__progname;

struct person
{
    int age; 
    int height; 
};

static struct person *johndoe;
static char report[255]; 

static void printout_struct(void* invar, char* structname){
    /* dumpstack(void) Got this routine from http://www.whitefang.com/unix/faq_toc.html
    ** Section 6.5. Modified to redirect to file to prevent clutter
    */
    /* This needs to be changed... */
    char dbx[160];

    sprintf(dbx, "echo 'p (struct %s)*%p\n' > gdbcmds", structname, invar );
    system(dbx);

    sprintf(dbx, "echo 'where\ndetach' | gdb -batch --command=gdbcmds %s %d > struct.dump", __progname, getpid() );
    system(dbx);

    sprintf(dbx, "cat struct.dump");
    system(dbx);

    return;
}

main ()
{

    johndoe = (struct person *)calloc(1, sizeof(struct person));

    johndoe->age = 6; 
    printout_struct(johndoe, "person"); 

    johndoe->age = 8; 
    printout_struct(johndoe, "person"); 

    printf("Hello World - age: %d\n:", johndoe->age);

    free(johndoe);
}


EOF

gcc -g -O0 $FN.c -o $FN

./$FN

  基本上最终显示了我想要的内容:

0x00740422 in __kernel_vsyscall ()
$1 = {age = 6, height = 0}
0x00740422 in __kernel_vsyscall ()
$1 = {age = 8, height = 0}
Hello World - age: 8

 

虽然,我不确定它是否适用于内核模块......

再次感谢您的帮助,
干杯!

编辑:我认为它不适用于内核模块的原因是,在这种情况下,我们有一个带有进程 ID 的用户态程序;我们只是gdb从这个程序中调用,同时向它指示我们的 PID - 所以gdb可以“附加”到我们的进程;然后,由于gdb还被指示使用调试符号加载可执行文件(因此它将“知道”结构是什么),并指示给定结构变量所在的地址,gdb然后可以打印输出结构。

对于内核模块 - 首先我不认为它们是具有唯一 PID 意义上的“进程”,所以gdb没有任何东西可以附加!事实上,有一个内核调试器,kgdb,它实际上可以闯入一个正在运行的内核并单步调试模块源代码;但是,您需要通过串行连接连接第二台机器 - 或虚拟机,请参阅Linux Hacks: Setting up kgdb using kvm/qemu

因此,无论如何,似乎gdb无法检查当前正在运行的主机内核的内存正在运行gdb- 但我会尝试进行实验,如果实验结果并非如此,我一定会发布:)

于 2010-07-22T18:27:16.783 回答
2

C 语言没有元数据,无论是编译时还是运行时。可能有一些特定于供应商的扩展来执行此操作。例如,doxygen将创建一个包含程序中每个结构类型的所有成员信息(名称和类型)的 XML 文件,编写一个程序来处理该 XML 文件并生成 printout_person 的代码并不难(const struct person*) 自动运行。

于 2010-07-22T16:48:35.573 回答
2

有关解析结构的一些信息,请参阅此相关问题。特别是我对pstruct的引用。

在您的情况下,您想从正在运行的程序内部获取信息,您必须调用其中一个外部工具,或者从可执行文件中解析出调试信息并适当地显示它。

您可能还会查看libgdb,尽管它看起来可能有些过时。

于 2010-07-22T17:01:28.630 回答
1

您必须添加描述结构的元信息,以便 printout_struct 可以完成其工作。否则,它无法猜测任何东西。尝试使用 gdb 删除所有调试信息,您会发现它不能“谈论”“年龄”或其他任何内容。

于 2010-07-22T16:42:07.290 回答
0

最近有人提到

旺盛的 ctags

在 stackoverflow 上执行类似的任务。也许你可以把它挖出来,不过我没有立即找到。

于 2010-07-22T16:54:09.023 回答
0

这是用户模式的更有效解决方案,而不是内核,它准备 gdb 脚本。然后使用脚本通过 gdb 执行应用程序。该脚本包含带有命令的断点。在源代码中需要用空定义标记断点:

#define gdb_print(v)

gdb_print(huge_struct);


gdb-print-prepare()
{
    # usage:
    # gdb-print-prepare $src > app.gdb
    # gdb --batch --quiet --command=app.gdb app
    cat  <<-EOF
    set auto-load safe-path /
    EOF
    grep --with-filename --line-number --recursive '^\s\+gdb_print(.*);' $1 | \
    while IFS=$'\t ;()' read line func var rest; do
        cat  <<-EOF
        break ${line%:}
        commands
        silent
        where 1
        echo \\n$var\\n
        print $var
        cont
        end
        EOF
    done
    cat  <<-EOF
    run
    bt
    echo ---\\n
    EOF
}

参考:https ://gitlab.com/makelinux/lib/blob/master/snippets/gdb-print-prepare.md

于 2018-10-18T15:28:37.550 回答