ELF格式解读-符号表
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF格式解读-符号表相关的知识,希望对你有一定的参考价值。
前言
我们常常调试错误说需要符号表,那么符号表是什么?符号表仅仅用来调试?
符号表本质就是一个映射表,举个例子:某行二进制汇编代码映射到源码第几行。
符号表的作用:
- 调试
- 重定位
调试
window工程有一个*.pdb
文件,里面编包含调试符号.可参阅wiki window pdb格式。
在java工程会使用一个叫混淆的技术,混淆后会生成混淆前后的映射文件mapping.txt
,这个也可以理解为符号表的一种。
在linux
有一个dwarf
的文件格式也是专门用于调试的文件。参阅wiki DWARF。
重定位
重定位可以大致分为两种类别动态重定位
与静态重定位
动态重定位
假设A程序需要xxx.so
中的yyyy
函数那么就需要从xxx.so
中的符号表进行读取。
静态重定位
我们看看下面的源代码
//mainA.c
int globalvar=0x123;
fun test()
//mainB.c
extern test();
extern int globalvar;
fun testFunB()
test();
printf("I am %d\\r\\n",globalvar)
我们程序有两个源代码,我们知道我编译的时候我们首先先将程序编译成目标文件。
也就是mainB.o mainA.o
,在目标文件中mainB.o
不知道test函数和globalvar变量的地址,因此我们需要在链接时修正mainB.o
函数调用地址。
在编译成目标文件时,编译器会把文件中所有的函数与变量地址放入一个符号表中。
在链接时把所有目标文件的符号表合成一个,然后利用重定位表和符号表完成函数调用地址修正。
我们看链接前
示意图:
链接后:
动态重定位
动态重定位和静态符号表原理都差不多,不过重定位操作延迟到调用时,关于延迟绑定可以参阅通过GDB学透PLT与GOT
静态符号表
#include <stdio.h>
static int mystaticVar = 3 ;
int myglobalvar=5;
int myglobalvar2=6;
extern void testfun();
int main()
int *inp= 0x00;
*inp=2;
testfun();
printf("hello world %d \\r\\n",mystaticVar);
return 0;
void hell()
testfun();
编译成目标文件:
gcc -c -o main.o main.c
首先我们查看对应头表
[图2-1]
可以看到一个.symtab
这个就是我们符号表数组起始地址
他的结构如下所示:
typedef struct
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
Elf32_Sym;//32位
typedef struct
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
Elf64_Sym;//64位
当然你依然可以使用相关命令查看
[图2-2]
st_name
数值表示该符号字符串位于字符串表(.)中的下标。
[图2-3]
我们以16进制打印符号表如下图:
[图2-4]
符号第0个字节为00
,第一个字节位6d
对应ascii字符为m
,而后遇到第一个00
之前组成的字符串就是main.c
st_info
这个字段是一个复合字段,他内部决定了符号bind
类型以及type
。
如下宏函数:
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf) )
#define ELF64_ST_BIND(i) ((i)>>4)
#define ELF64_ST_TYPE(i) ((i)&0xf)
#define ELF64_ST_INFO(b,t) (((b)<<4)+((t)&0xf ))
bind
bind相关字段枚举如下图所示
其中我只需要留意三个字段STB_LOCAL
,STB_GLOBAL
,STB_WEAK
STB_LOCAL
:
表示这个符号仅仅在本目标文件可见,其他目标文件不可见。比如静态方法和静态属性。比如本例代码mystaticVar
就是对外不可见的。
STB_GLOBAL
目标文件中对外暴露的函数和属性如本例myglobalvar2
STB_WEAK
弱符号,这里不扩开讲
type
说明这个符号是什么,比如是函数还是文件或者属性
STT_NOTYPE
说明这个符号什么都不是,符号表第一项固定为此类型,以及引用外部的函数等。
STT_OBJECT
一般说明这个符号是变量数组等
STT_FUNC
一般指代函数
st_other
这个字段用于控制可见性,其枚举值如下图所示
因为STV_DEFAULT
是最常见的属性也是默认属性,这里只说明此项:
如果st_other是STV_DEFAULT
那么可见性交付给符号表中st_info
决定
st_shndx
符号关联的节下标。
比如我们的全局变量myglobalvar2
位于.data节中(在节头表下标是3)
再看看函数hell信息
其中1是节头表中的.text
st_value
这个数值根据文件不同具有不同同的意义(这里直接翻译文档,所以会有些生硬)
-
在可重定位文件中,st_value 包含节索引为 SHN_COMMON 的符号的对齐约束。
-
在可重定位文件中,st_value 包含所定义符号的节偏移。st_value 表示从 st_shndx 所标识的节的起始位置的偏移。
-
在可执行文件和共享目标文件中,st_value 包含虚拟地址。为使这些文件的符号更适用于运行时链接程序,节偏移(文件解释)会替换为与节编号无关的虚拟地址(内存解释)。
我们这里举例第2点
在main.o
中他是一个可重定位文件。
main.o的hell符号表的value为0x48
,size大小为21
他表示函数位于.text位移48h
,且函数体大小为21(十进制,16进制为15).
我们通过计算 函数首尾地址得到大小:5c-48+1=15h
我们再看看对于变量差别
myglobalvar节内偏移为4,大小为4(因为变量大小为4)
st_size
上文已解释
动态符号表
//main.c
#include <stdio.h>
static int mystaticVar = 3 ;
int myglobalvar=5;
int myglobalvar2=6;
extern void testfun();
int main()
testfun();
printf("hello world %d \\r\\n",mystaticVar);
return 0;
void hell()
testfun();
//test.c
__attribute__((visibility("default"))) void testfun()
__attribute__((visibility("hidden"))) void testfun2()
int libGLobal=2;
相关编译命令:
gcc -fPIC -shared test.c -o test.so
gcc main.c -o main.out test.so
此时我们查看可执行文件的节头
你会发下多了两个.dynsym
和.dynstr
.
他们其实作用和.symtab
与.strtab
作用一样的,不过专门作用动态库。
如下图:
我们最后看到test.c有两个很陌生的符号
__attribute__((visibility("hidden")))
__attribute__((visibility("default")))
上面两个是传递给编译器使用的,告诉这个函数或者变量是否需要导出到符号表,如果不导出其他程序无法使用,默认所有符号都是 __attribute__((visibility("default")))
.
我们readelf -s test.so
查看导出的符号
那你会发现没有testfun2
在.dynsym
以上是关于ELF格式解读-符号表的主要内容,如果未能解决你的问题,请参考以下文章