2

Linux 命令nm可以列出符号表。但是我不知道里面的符号类型。

nm命令不知道符号是函数还是全局值。

目前我正在构建一个符号表以启用固件中的模块支持,但如果我不知道符号是表示功能还是有价值,我将无法正确初始化。

例如,假设我的符号表结构是:

struct symtab { 
  const char *name; 
  void *addr; 
  int isfunc; 
};

然后我想添加一个关于“printk”的条目。我知道 printk 是一个函数,所以条目是 {"printk", printk, 1}。但是,如果我不知道这一点并将printk其视为变量,那么问题就来了。条目 { "printk", &printk, 0 } 将导致错误。

我调查了 Grub 2.00 的代码,发现函数原型中使用了一些标签,如“EXPORT_FUNC”或“EXPORT_VAR”,void EXPORT_FUNC (usb_open) (void)以便一些脚本可以使用这些标签来生成正确的符号表。

我的问题是我当前的项目没有这些标签。

有没有办法知道符号是否是函数?

4

2 回答 2

3

您使用的是 Linux,所以您的目标文件是 ELF 格式,对吗?是这样,使用objdump -t,这样您就可以看到标志和每个符号所在的部分。输出在man 1 objdump手册页中的-t选项下进行了描述,大约四分之三。

例如,考虑这个文件:

extern int reference;

int initialized_variable = 1;

static int static_initialized_variable = 2;

const int const_variable = 3;

static const int static_const_variable = 4;

int variable;

static int static_variable;

int function(void)
{
    return 5;
}

static int static_function(void)
{
    return 6;
}

gcc -c example.c在 x86-64 上使用 gcc-4.6.3编译,objdump -t example.o产生

example.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 example.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000004 l     O .data  0000000000000004 static_initialized_variable
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000004 l     O .rodata    0000000000000004 static_const_variable
0000000000000000 l     O .bss   0000000000000004 static_variable
000000000000000b l     F .text  000000000000000b static_function
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000000 g     O .data  0000000000000004 initialized_variable
0000000000000000 g     O .rodata    0000000000000004 const_variable
0000000000000004       O *COM*  0000000000000004 variable
0000000000000000 g     F .text  000000000000000b function

第二列实际上是固定宽度的,七个标志字符:

                 ^^^^^^^

标志字符在objdump 手册页中描述如下:

The flag characters are divided into 7 groups as follows:

       "l"
       "g"
       "u"
       "!" The symbol is a local (l), global (g), unique global (u),
           neither global nor local (a space) or both global and local
           (!).  A symbol can be neither local or global for a variety
           of reasons, e.g., because it is used for debugging, but it is
           probably an indication of a bug if it is ever both local and
           global.  Unique global symbols are a GNU extension to the
           standard set of ELF symbol bindings.  For such a symbol the
           dynamic linker will make sure that in the entire process
           there is just one symbol with this name and type in use.

       "w" The symbol is weak (w) or strong (a space).

       "C" The symbol denotes a constructor (C) or an ordinary symbol (a
           space).

       "W" The symbol is a warning (W) or a normal symbol (a space).  A
           warning symbol's name is a message to be displayed if the
           symbol following the warning symbol is ever referenced.

       "I"
       "i" The symbol is an indirect reference to another symbol (I), a
           function to be evaluated during reloc processing (i) or a
           normal symbol (a space).

       "d"
       "D" The symbol is a debugging symbol (d) or a dynamic symbol (D)
           or a normal symbol (a space).

       "F"
       "f"
       "O" The symbol is the name of a function (F) or a file (f) or an
           object (O) or just a normal symbol (a space).

您可以依赖标志,也可以根据代码通常驻留在.text节中、节中的只读数据、.rodata节中的初始化数据以及.data节中未初始化的数据这一事实来推断符号类型.bss。请注意,使用 GCC 扩展时,节名可能会得到一个后缀;例如,要在启动时自动执行的函数 ( __attribute__((constructor))),通常会在一个.text.startup节中结束。因此,对部分名称进行前缀匹配,而不是完全匹配。


对于自定义固件,您可以使用多种方法来提供模块支持。如果固件二进制文件使用 ELF 格式,则可以直接解析上述信息。例如,Linux 内核使用这种方法。(为了好玩,试试objdump -t内核.ko模块/lib/modules/$(uname -r)/。)

对于微控制器,ELF 支持是多余的。如果您希望您的固件能够按需加载模块,您仍然需要一个动态链接器(并且比固件二进制文件的 ELF 格式更简单)。瓶颈通常是可用的 RAM 量。如果您有很多,并且您需要您的固件能够在运行时加载新模块,请使用 ELF。

能够在工作站上将不同的模块(甚至可能是静态配置数据)链接在一起,以“构建”一个完整的新固件映像,然后将该 blob 加载到设备中,通常效率更高。(因此,blob 通常是带有 CRC 校验的内存映像。)为此,使用 C、C++ 或什至使用更高级别的可移植语言(如 Python)编写的 GUI 应用程序(其中有许多 ELF 模块可供您使用)以便携式方式进行;一个“配置器”应用程序,如果你愿意的话。开发人员将使用普通工具链来生成 ELF 格式的二进制模块,最终用户可以在配置器应用程序中组合这些模块以生成固件映像。

于 2013-09-27T01:47:54.277 回答
0

使用 readelf 实用程序获取有关目标文件的所有详细信息。有关 readelf 的更多信息,man readelf它提供了符号表中符号的所有详细信息。

于 2013-09-27T06:05:53.187 回答