# FRR DWARF structure definition extractor # # Copyright (C) 2020 David Lamparter for NetDEF, Inc. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along # with this program; see the file COPYING; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import sys import os import subprocess import re import argparse import json structs = ['xref', 'xref_logmsg', 'xref_threadsched', 'xref_install_element', 'xrefdata', 'xrefdata_logmsg', 'cmd_element'] def extract(filename='lib/.libs/libfrr.so'): ''' Convert output from "pahole" to JSON. Example pahole output: $ pahole -C xref lib/.libs/libfrr.so struct xref { struct xrefdata * xrefdata; /* 0 8 */ enum xref_type type; /* 8 4 */ int line; /* 12 4 */ const char * file; /* 16 8 */ const char * func; /* 24 8 */ /* size: 32, cachelines: 1, members: 5 */ /* last cacheline: 32 bytes */ }; ''' pahole = subprocess.check_output(['pahole', '-C', ','.join(structs), filename]).decode('UTF-8') struct_re = re.compile(r'^struct ([^ ]+) \{([^\}]+)};', flags=re.M | re.S) field_re = re.compile(r'^\s*(?P[^;\(]+)\s+(?P[^;\[\]]+)(?:\[(?P\d+)\])?;\s*\/\*(?P.*)\*\/\s*$') comment_re = re.compile(r'^\s*\/\*.*\*\/\s*$') pastructs = struct_re.findall(pahole) out = {} for sname, data in pastructs: this = out.setdefault(sname, {}) fields = this.setdefault('fields', []) lines = data.strip().splitlines() next_offs = 0 for line in lines: if line.strip() == '': continue m = comment_re.match(line) if m is not None: continue m = field_re.match(line) if m is not None: offs, size = m.group('comment').strip().split() offs = int(offs) size = int(size) typ_ = m.group('type').strip() name = m.group('name') if name.startswith('(*'): # function pointer typ_ = typ_ + ' *' name = name[2:].split(')')[0] data = { 'name': name, 'type': typ_, # 'offset': offs, # 'size': size, } if m.group('array'): data['array'] = int(m.group('array')) fields.append(data) if offs != next_offs: raise ValueError('%d padding bytes before struct %s.%s' % (offs - next_offs, sname, name)) next_offs = offs + size continue raise ValueError('cannot process line: %s' % line) return out class FieldApplicator(object): ''' Fill ELFDissectStruct fields list from pahole/JSON Uses the JSON file created by the above code to fill in the struct fields in subclasses of ELFDissectStruct. ''' # only what we really need. add more as needed. packtypes = { 'int': 'i', 'uint8_t': 'B', 'uint16_t': 'H', 'uint32_t': 'I', 'char': 's', } def __init__(self, data): self.data = data self.classes = [] self.clsmap = {} def add(self, cls): self.classes.append(cls) self.clsmap[cls.struct] = cls def resolve(self, cls): out = [] #offset = 0 fieldrename = getattr(cls, 'fieldrename', {}) def mkname(n): return (fieldrename.get(n, n),) for field in self.data[cls.struct]['fields']: typs = field['type'].split() typs = [i for i in typs if i not in ['const']] # this will break reuse of xrefstructs.json across 32bit & 64bit # platforms #if field['offset'] != offset: # assert offset < field['offset'] # out.append(('_pad', '%ds' % (field['offset'] - offset,))) # pretty hacky C types handling, but covers what we need ptrlevel = 0 while typs[-1] == '*': typs.pop(-1) ptrlevel += 1 if ptrlevel > 0: packtype = ('P', None) if ptrlevel == 1: if typs[0] == 'char': packtype = ('P', str) elif typs[0] == 'struct' and typs[1] in self.clsmap: packtype = ('P', self.clsmap[typs[1]]) elif typs[0] == 'enum': packtype = ('I',) elif typs[0] in self.packtypes: packtype = (self.packtypes[typs[0]],) elif typs[0] == 'struct': if typs[1] in self.clsmap: packtype = (self.clsmap[typs[1]],) else: raise ValueError('embedded struct %s not in extracted data' % (typs[1],)) else: raise ValueError('cannot decode field %s in struct %s (%s)' % ( cls.struct, field['name'], field['type'])) if 'array' in field and typs[0] == 'char': packtype = ('%ds' % field['array'],) out.append(mkname(field['name']) + packtype) elif 'array' in field: for i in range(0, field['array']): out.append(mkname('%s_%d' % (field['name'], i)) + packtype) else: out.append(mkname(field['name']) + packtype) #offset = field['offset'] + field['size'] cls.fields = out def __call__(self): for cls in self.classes: self.resolve(cls) def main(): argp = argparse.ArgumentParser(description = 'FRR DWARF structure extractor') argp.add_argument('-o', dest='output', type=str, help='write JSON output', default='python/xrefstructs.json') argp.add_argument('-i', dest='input', type=str, help='ELF file to read', default='lib/.libs/libfrr.so') args = argp.parse_args() out = extract(args.input) with open(args.output + '.tmp', 'w') as fd: json.dump(out, fd, indent=2, sort_keys=True) os.rename(args.output + '.tmp', args.output) if __name__ == '__main__': main()