|
#!/usr/bin/python3 |
|
## |
|
## This file is part of the sigrok-util project. |
|
## |
|
## Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se> |
|
## |
|
## 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 3 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; if not, see <http://www.gnu.org/licenses/>. |
|
## |
|
|
|
import struct |
|
|
|
class elf: |
|
|
|
def read_struct(this, struct_fmt, struct_fields): |
|
fmt = this.elf_endianprefix+str.translate(struct_fmt, this.elf_format); |
|
fields = struct.unpack(fmt, this.file.read(struct.calcsize(fmt))) |
|
return dict(zip(struct_fields, fields)) |
|
|
|
def read_ehdr(this): |
|
return this.read_struct('16sHHWNNNWHHHHHH', |
|
('e_ident', 'e_type', 'e_machine', 'e_version', |
|
'e_entry', 'e_phoff', 'e_shoff', 'e_flags', |
|
'e_ehsize', 'e_phentsize', 'e_phnum', |
|
'e_shentsize', 'e_shnum', 'e_shstrndx')) |
|
|
|
def read_shdr(this): |
|
return this.read_struct('WWNNNNWWNN', |
|
('sh_name', 'sh_type', 'sh_flags', 'sh_addr', |
|
'sh_offset', 'sh_size', 'sh_link', 'sh_info', |
|
'sh_addralign', 'sh_entsize')) |
|
|
|
def read_section(this, shdr): |
|
this.file.seek(shdr['sh_offset']) |
|
return this.file.read(shdr['sh_size']) |
|
|
|
def get_name(this, name, strtab=None): |
|
strtab = strtab or this.strtab |
|
nul = strtab.find(b'\0', name) |
|
if nul < 0: |
|
return bytes.decode(strtab[name:]) |
|
else: |
|
return bytes.decode(strtab[name:nul]) |
|
|
|
def find_section(this, name): |
|
for section in this.shdrs: |
|
if this.get_name(section['sh_name']) == name: |
|
return section |
|
raise KeyError(name) |
|
|
|
def parse_symbol(this): |
|
if this.elf_wordsize == 64: |
|
return this.read_struct('WBBHNX', |
|
('st_name', 'st_info', 'st_other', |
|
'st_shndx', 'st_value', 'st_size')) |
|
else: |
|
return this.read_struct('WNWBBH', |
|
('st_name', 'st_value', 'st_size', |
|
'st_info', 'st_other', 'st_shndx')) |
|
|
|
def parse_rela(this): |
|
return this.read_struct('NNn', ('r_offset', 'r_info', 'r_addend')) |
|
|
|
def parse_rel(this): |
|
return this.read_struct('NN', ('r_offset', 'r_info')) |
|
|
|
def fixup_reloc(this, reloc): |
|
if not 'r_addend' in reloc: |
|
reloc['r_addend'] = 0 |
|
if this.elf_wordsize == 64: |
|
reloc['r_sym'] = reloc['r_info'] >> 32 |
|
reloc['r_type'] = reloc['r_info'] & 0xffffffff |
|
else: |
|
reloc['r_sym'] = reloc['r_info'] >> 8 |
|
reloc['r_type'] = reloc['r_info'] & 0xff |
|
return reloc |
|
|
|
def parse_symbols(this, symsecname, strsecname): |
|
try: |
|
symsechdr = this.find_section(symsecname) |
|
strsechdr = this.find_section(strsecname) |
|
except KeyError: |
|
return {} |
|
strsec = this.read_section(strsechdr) |
|
this.file.seek(symsechdr['sh_offset']) |
|
syms = [dict(this.parse_symbol(),number=i) for i in |
|
range(0, symsechdr['sh_size'] // symsechdr['sh_entsize'])] |
|
return {this.get_name(sym['st_name'], strsec): sym for sym in syms} |
|
|
|
def parse_relocs(this, section): |
|
this.file.seek(section['sh_offset']) |
|
if section['sh_type'] == 4: |
|
relocs = [this.fixup_reloc(this.parse_rela()) for i in |
|
range(0, section['sh_size'] // section['sh_entsize'])] |
|
else: |
|
relocs = [this.fixup_reloc(this.parse_rel()) for i in |
|
range(0, section['sh_size'] // section['sh_entsize'])] |
|
return relocs |
|
|
|
def address_to_offset(this, addr): |
|
for section in this.shdrs: |
|
if (section['sh_addr'] <= addr and |
|
section['sh_addr']+section['sh_size'] > addr): |
|
return section['sh_offset']+(addr-section['sh_addr']) |
|
raise IndexError('address out of range') |
|
|
|
def load_symbol(this, sym): |
|
this.file.seek(this.address_to_offset(sym['st_value'])) |
|
return this.file.read(sym['st_size']) |
|
|
|
def __init__(this, filename): |
|
this.file = open(filename, 'rb') |
|
magic = this.file.read(16) |
|
|
|
if magic[:4] != b'\x7fELF': |
|
raise Exception("ELF signature not found") |
|
|
|
if magic[4] == 1: |
|
this.elf_wordsize = 32 |
|
nativeint = 'Ii' |
|
elif magic[4] == 2: |
|
this.elf_wordsize = 64 |
|
nativeint = 'Qq' |
|
else: |
|
raise Exception("Invalid ELF file class") |
|
|
|
if magic[5] == 1: |
|
this.elf_endianprefix = '<' |
|
elif magic[5] == 2: |
|
this.elf_endianprefix = '>' |
|
else: |
|
raise Exception("Invalid ELF data encoding") |
|
|
|
this.elf_format = str.maketrans('HWwXxNn', 'HIiQq'+nativeint) |
|
|
|
this.file.seek(0) |
|
this.ehdr = this.read_ehdr() |
|
this.file.seek(this.ehdr['e_shoff']) |
|
this.shdrs = [this.read_shdr() for i in range(this.ehdr['e_shnum'])] |
|
|
|
this.strtab = this.read_section(this.shdrs[this.ehdr['e_shstrndx']]) |
|
|
|
this.symtab = this.parse_symbols('.symtab', '.strtab') |
|
this.dynsym = this.parse_symbols('.dynsym', '.dynstr') |
|
|
|
this.relocs = {} |
|
for section in this.shdrs: |
|
if section['sh_type'] == 4 or section['sh_type'] == 9: |
|
rels = {} |
|
symsec = this.shdrs[section['sh_link']] |
|
if this.get_name(symsec['sh_name']) == '.symtab': |
|
rels['symbols'] = this.symtab |
|
elif this.get_name(symsec['sh_name']) == '.dynsym': |
|
rels['symbols'] = this.dynsym |
|
rels['relocs'] = this.parse_relocs(section) |
|
this.relocs[this.get_name(section['sh_name'])] = rels |
|
|
|
def __del__(this): |
|
try: |
|
this.file.close() |
|
except AttributeError: |
|
pass |