ELF格式解读-符号表

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF格式解读-符号表相关的知识,希望对你有一定的参考价值。

前言

一个优先的symtab文章

我们常常调试错误说需要符号表,那么符号表是什么?符号表仅仅用来调试?

符号表本质就是一个映射表,举个例子:某行二进制汇编代码映射到源码第几行。

符号表的作用:

  1. 调试
  2. 重定位

调试

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

这个数值根据文件不同具有不同同的意义(这里直接翻译文档,所以会有些生硬)

  1. 在可重定位文件中,st_value 包含节索引为 SHN_COMMON 的符号的对齐约束。

  2. 在可重定位文件中,st_value 包含所定义符号的节偏移。st_value 表示从 st_shndx 所标识的节的起始位置的偏移。

  3. 在可执行文件和共享目标文件中,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格式解读-符号表的主要内容,如果未能解决你的问题,请参考以下文章

ELF格式解读 Dynamic节

ELF格式解读 Dynamic节

ELF格式解读-程序头与内存布局

ELF格式解读-程序头与内存布局

找不到符号表(Elf 格式)(C 编程)

Android 逆向ELF 文件格式 ( 程序头数据 | 节区头数据 | 动态符号表 )