ARM学习(7) symbol 符号表以及调试

31 篇文章 32 订阅

ARM学习(7) 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;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张一西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值