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)
),这样一些脚本就可以使用这些标签生成一个正确的符号表。
我的问题是我当前的项目没有这些标签。
有没有一种方法可以知道符号是否是函数?
答案 0 :(得分: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;
}
在x86-64上使用gcc-4.6.3使用gcc -c example.c
编译,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 man page中描述如下:
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 ++编写的GUI应用程序,甚至是Python等更高级别的可移植语言(可以使用许多ELF模块)以便携的方式做到这一点;一个“配置器”应用程序,如果你愿意的话。开发人员将使用普通工具链来生成ELF格式的二进制模块,最终用户可以在配置器应用程序中将这些模块组合在一起以生成固件映像。
答案 1 :(得分:0)
使用readelf实用程序获取有关目标文件的所有详细信息。有关readelf man readelf
的更多信息,它会提供有关符号表中符号的所有详细信息。