ARM学习(7) symbol 符号表以及调试
笔者来聊聊symbol符号的理解
symbol 符号表以及调试
1、symbol 符号表认识
symbol 符号就是变量名或者函数名,可以理解为一种调试信息,可以通过符号来找对相应函数或者变量的地址,从而可以进行调试。
但是并不是所有的函数名或者变量名都会生成符号,以下几种情况不会生成对应得符号。
- 内联函数
- 定义但没有用到的函数
- 定义但没有用到的变量
- 一些常量数值 比如a = 1000;这种可能会被优化成具体的值进行替代该符号。
- 一些只调用1-2次的静态函数,可能会被优化成内联函数,从而也没有符号。
2、symbol符号表的生成
readelf
Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections' header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
--dyn-syms Display the dynamic symbol table
readelf -s AdvancedClock.axf > symbol.txt
3、symbol符号表调试
如何基于符号表进行调试呢,我们首先想个思路出来,就是我们知道了符号 的地址,可以通过操作地址,进而进行调试,看来在一些嵌入式领域,地址还是很重要,有了地址可以干很多事情。
- 函数地址:可以直接调用, 函数指针被赋值函数地址,然后传递参数调用,则可以调用函数,
- 全局变量地址:知道变量的地址以及长度,可以将变量的值打印出来,方便调试,类似于gdb的调试一样,
那接下来我们就看一下,如何吧这些信息装到MCU或者SOC里面,上面介绍过有map表记录了这些信息,那我们就把这些数据组织起来,下发到MCU里面,那么就可以实现符号表调试了。
- 串口CLI 命令可以动态调用符号进去调试
- OS发现错误时,可以知道通过函数地址,找到函数符号,打印出来
- CoreDump:程序异常进入coredump时,可以知道哪个函数引起的coredump,根据硬件抓到的错误可以看出来符号。
3.1 方案1
通过二次编译,将这些数据直接链接到指定的Flash空间,那么就可以使用这些符号信息,当然这个文件需要首次编译后生成,生成对应的.c文件,然后第二次编译进去,
优势:简单,静态链接进去,知道地址后,可以根据地址直接进入读取访问,
劣势:二次编译地址可能变动,导致无法使用。
3.2 方案2
一次编译,然后在运行时,动态通过某种通讯方式(USB/UART)写入到内部的Flash或者RAM区域进行访问。
优势:无需多次编译,也不需要考虑地址变化,
劣势:无法直接使用,需要动态“烧录”,比较麻烦,每次编译代码之后,都需要重新“进去”进去
3.3 数据组织
使用数据结构来组织这一张表,首先我们要知道我们的需求,然后想如何组织
我们需要知道符号的地址,符号的name,符号的长度,符号的属性(比如是函数,还是变量)
- 需要从地址知道名字
- 从名字知道地址
这样下来我们就需要两张表,一张表是addir_to_name,一张表是name_to_addr,接着我们来组织一下数据。
需要注意以下几点:
- 两张表中都含有name,即symbol str,符号表字符串,那么我们可以把全部字符串放到一块,然后通过一个索引周到name,节省空间
- 其实可以使用一张表,比如知道地址到名字,那么名字到地址的时候,需要找到这张表中名字的偏移,然后一个个去搜索,这样效率较低,可以再安排一张表,名字顺序从小到大,可以二分查找。
- 组织数据的时候,需要知道字符串的起始偏移位置,两张表的起始位置。
所以根据上面的设计,我们可以给出如下的结构体信息,文件头的结构体,两张表的结构体
typedef struct symbol_summary_struct
{
unsigned int file_size;
unsigned int header_data_size;
unsigned int crc;
unsigned int sumbol_name_2_addr_cnt;
unsigned int sumbol_addr_2_name_cnt;
unsigned int symbol_name_str_position;
unsigned int symbol_addr_2_name_position;
unsigned int symbol_name_2_addr_position;
}symbol_summary_t;
typedef struct symbol_addr_2_name_attr_struct
{
unsigned int addr;
unsigned short name_index;
unsigned short length;
unsigned short attr;
unsigned short padding;
}symbol_addr_2_name_attr_t;
typedef struct symbol_name_2_addr_attr_struct
{
unsigned short name_index;
unsigned short length;
unsigned int addr;
unsigned short attr;
unsigned short padding;
}symbol_name_2_symbol_attr_t;
我们可以看到上面的两张表很相似,一个是按addr进行排序的一张表,一个是按照name进行排序的一张表,看起来是有点重复,我们可以进行优化成如下的一张表,其实symbol_index返回的是addr排序的那张表的其中一个symbol的索引,就是说我们知道了这个索引,就可以知道addr lenth这些信息,节省了空间。
typedef struct symbol_name_2_addr_attr_struct
{
unsigned short int name_index;
unsigned short int symbol_index;
}symbol_name_2_symbol_attr_t;
3.4 python解析map,生成上面数据
话不多说,直接上解析代码
class Symbol:
def __init__(self, symbol_file_path, symbol_bin_file_path):
self.symbol_file_path = symbol_file_path
self.symbol_bin_file_path = symbol_bin_file_path
self.sym_start_str = "Symbol table"
self.symbs = {}
self.name_2_symbs = {}
self.symbol_cnt = 0
self.name_2_symbol_cnt = 0
self.file_size = 0
self.header_bytes = 32
self.string_offset = 0
self.symbol_offset = 0
self.name_2_symbol_offset = 0
self.same_name_string_num = 0
def parsing_symbol_file(self):
with open(self.symbol_file_path, "r") as f:
while True:
data = f.readline()
if not data:
self.symbs = dict(sorted(self.symbs.items(), key=operator.itemgetter(0)))
break
if self.sym_start_str in data:
continue
data_list = data.split()
if len(data_list) < 8:
continue
# 3423: 0802bbb1 144 FUNC GLOBAL HIDDEN 1 fml_get_submit_info_data =======> read data
# index: addr length FUNC GLOBAL ATTR 1 name
# self.symbols[0x1000] = {-1, 0x1000, "name", -1, 8, 1}
if len(data_list) >= 4:
entry_flag = False
if data_list[3] == "FUNC" and data_list[4] == "GLOBAL":
attribute = 2 | 4
entry_flag = True
if data_list[3] == "FUNC" and data_list[4] == "LOCAL":
attribute = 2 | 8
entry_flag = True
if data_list[3] == "OBJECT" and data_list[4] == "GLOBAL":
attribute = 1 | 4
entry_flag = True
if data_list[3] == "OBJECT" and data_list[4] == "LOCAL":
attribute = 1 | 8
entry_flag = True
if entry_flag:
entry_flag = False
if int("0x" + data_list[1], base=16) not in self.symbs.keys():
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(-1) # symbol index
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(
int("0x" + data_list[1], base=16)) # addr
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(data_list[7])
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(-1) # name offset
if data_list[2].isdecimal():
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append( # len
int(data_list[2], base=10))
else:
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(
int(data_list[2], base=16))
self.symbs.setdefault(int("0x" + data_list[1], base=16), []).append(attribute) # attr
print("addr:{:8x} name:{:30} len:{}".format(int("0x" + data_list[1], base=16), data_list[7],
data_list[2]))
def update_symbol_index(self):
index = 0
for i in self.symbs.keys():
self.symbs[i][0] = index
index = index + 1
def update_symbol_str_offset(self):
offset = 0
for i in self.symbs.keys():
self.symbs[i][3] = offset
offset = offset + len(self.symbs[i][2])
offset = offset + 1
self.string_offset = offset
def update_name_2_addr_index(self):
for i in self.symbs.keys():
if self.symbs[i][2] in self.name_2_symbs.keys():
self.same_name_string_num = self.same_name_string_num + 1
else:
self.name_2_symbs.setdefault(self.symbs[i][2], []).append(self.symbs[i][3])
self.name_2_symbs.setdefault(self.symbs[i][2], []).append(self.symbs[i][0])
print("same symbol cnt:{}".format(self.same_name_string_num))
def generate_symbol_bin_file(self):
self.update_symbol_index()
self.update_symbol_str_offset()
self.update_name_2_addr_index()
if 0 != self.string_offset % 4:
self.string_offset = self.string_offset + (4 - self.string_offset % 4)
self.name_2_symbs = dict(sorted(self.name_2_symbs.items(), key=operator.itemgetter(0)))
self.symbol_cnt = len(self.symbs)
self.name_2_symbol_cnt = len(self.name_2_symbs)
self.symbol_offset = self.string_offset
self.name_2_symbol_offset = self.symbol_offset + self.symbol_cnt * 24
self.file_size = self.header_bytes + self.name_2_symbol_offset * 8
print("file size:{} symbol cnt:{} name_2_sym cnt: {} ".format(self.file_size, self.symbol_cnt,
self.name_2_symbol_cnt))
with open(self.symbol_bin_file_path, "wb") as f:
f.seek(0)
f.write(struct.pack('I', self.file_size))
f.write(struct.pack('I', self.header_bytes))
f.write(struct.pack('I', 0))
f.write(struct.pack('I', self.symbol_cnt))
f.write(struct.pack('I', self.name_2_symbol_cnt))
f.write(struct.pack('I', 0))
f.write(struct.pack('I', self.symbol_offset))
f.write(struct.pack('I', self.name_2_symbol_offset))
for i in self.symbs.keys():
f.write(self.symbs[i][2].encode("utf-8"))
f.write("\0".encode("utf-8"))
f.seek(self.symbol_offset)
for i in self.symbs.keys():
f.write(struct.pack('I', self.symbs[i][1]))
f.write(struct.pack('H', self.symbs[i][3]))
f.write(struct.pack('H', self.symbs[i][4]))
f.write(struct.pack('H', self.symbs[i][5]))
f.write(struct.pack('H', 0x00))
for i in self.name_2_symbs.keys():
f.write(struct.pack('H', self.name_2_symbs[i][0]))
f.write(struct.pack('H', self.name_2_symbs[i][1]))
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
if 3 == len(sys.argv):
symbol = Symbol(str(sys.argv[1]), str(sys.argv[2]))
symbol.parsing_symbol_file()
else:
symbol = Symbol("symbol.txt", "symbol.bin")
symbol.parsing_symbol_file()
symbol.generate_symbol_bin_file()
上面主要分为两步:
- 解析symbol符号,将数据 地址、名字、长度等信息存在字典里面
- 将字典里面的数据,按照设计的表进行写到bin文件中
3.5 MCU解析数据,二分搜索
首先加载已经放到指定链接地址得bin,
typedef struct sym_file_bin_struct
{
symbol_summary_t* sym_bin_file;
const char* string_list;
symbol_addr_2_name_attr_t* addr_2_name_list;
symbol_name_2_symbol_attr_t* name_2_addr_list;
}sym_file_t
首先获取字符串表、以及相互转换得两张表得地址,然后就可以通过结构体索引来获取某个符号得相关信息。
void symbol_file_init()
{
symbol_file_g.sym_bin_file = (symbol_summary_t*)0x08004000;
symbol_file_g.string_list = (char*)(symbol_file_g.sym_bin_file+1);
symbol_file_g.addr_2_name_list = (symbol_addr_2_name_attr_t*)((unsigned char*)(symbol_file_g.sym_bin_file+1)+ symbol_file_g.sym_bin_file->symbol_addr_2_name_position);
symbol_file_g.name_2_addr_list = (symbol_name_2_symbol_attr_t*)((unsigned char*)(symbol_file_g.sym_bin_file+1)+ symbol_file_g.sym_bin_file->symbol_name_2_addr_position);
}
unsigned int symbol_addr_2_name_cnt()
{
return symbol_file_g.sym_bin_file->sumbol_addr_2_name_cnt;
}
unsigned int symbol_name_2_addr_cnt()
{
return symbol_file_g.sym_bin_file->sumbol_name_2_addr_cnt;
}
总共有两种情况,
- 地址找名字
char* symbol_addr_2_name(unsigned int addr)
{
int low,mid,high;
low = 0;
high = symbol_addr_2_name_cnt()-1;
while(low <= high)
{
mid = (low+high)>>2;
if(addr == symbol_file_g.addr_2_name_list[mid].addr)
{
return symbol_file_g.string_list[symbol_file_g.addr_2_name_list[mid].name_index];
}
else if(addr < symbol_file_g.addr_2_name_list[mid].addr)
{
high = mid -1;
}
else
{
low = mid + 1;
}
}
if(addr > symbol_file_g.addr_2_name_list[mid].addr)
{
return symbol_file_g.string_list[symbol_file_g.addr_2_name_list[mid].name_index];
}
else
{
return symbol_file_g.string_list[symbol_file_g.addr_2_name_list[mid-1].name_index];
}
}
- 名字找地址
int symbol_name_2_addr(char* name)
{
int low,mid,high;
low = 0;
high = symbol_name_2_addr_cnt()-1;
while(low <= high)
{
mid = (low+high)>>2;
int res = strcmp(name,symbol_file_g.string_list[symbol_file_g.name_2_addr_list[mid].name_index]);
if(0 == res)
{
return symbol_file_g.addr_2_name_list[symbol_file_g.name_2_addr_list[mid].symbol_index].addr;
}
else if(0 < res)
{
high = mid -1;
}
else
{
low = mid + 1;
}
}
return -1;
}