Search code examples
pythonelfavrpyelftools

Getting the memory layout out of an (avr)elf file by useing python + pyElftools


I am creating my own bootloader for an ATXmega128A4U. To use the bootloader I want to transform the ELF-file of the firmware into a memory map used in the the ATXmega. For that I use python and the modul "pyelftools". The documentation of it is poor and so I run into a problem: I do not know what information I can use to get the address, offset etc. from the data at the sections. My goal is to create a bytearray, copy the data/code into it and transfer it to the bootlaoder. Below is my code:

import sys

# If pyelftools is not installed, the example can also run from the root or
# examples/ dir of the source distribution.
sys.path[0:0] = ['.', '..']

from elftools.common.py3compat import bytes2str
from elftools.elf.elffile import ELFFile

# 128k flash for the ATXmega128a4u
flashsize = 128 * 1024


def process_file(filename):
    with open(filename, 'rb') as f:
        # get the data
        elffile = ELFFile(f)
        dataSec = elffile.get_section_by_name(b'.data')        
        textSec = elffile.get_section_by_name(b'.text')
        # prepare the memory
        flashMemory = bytearray(flashsize)
        # the data section
        startAddr = dataSec.header.sh_offset
        am = dataSec.header.sh_size
        i = 0
        while i < am:
            val = dataSec.stream.read(1)
            flashMemory[startAddr] = val[0]
            startAddr += 1
            i += 1
        # the text section
        startAddr = textSec.header.sh_offset
        am = textSec.header.sh_size
        i = 0
        while i < am:
            print(str(startAddr) + ' : ' + str(i))
            val = textSec.stream.read(1)
            flashMemory[startAddr] = val[0]
            startAddr += 1
            i += 1
    print('finished')

if __name__ == '__main__':
    process_file('firmware.elf')

Hope someone can tell me how to solve this problem.


Solution

  • I manged to solve the problem. don't read the data manualy from the stream by "textSec.stream.read" use "textSec.data()" instead. Internaly (see "sections.py") a seek operation in the file is done. Afterwards the data is read. The result will be the valid data chunk. The following code reads the code(text) section of a atxmega firmware and copies it into a bytearray which has the layout of the flash of an atxmega128a4u device. @vlas_tepesch: the hex conversation is not needed and the the 64k pitfall is avoided.

    sys.path[0:0] = ['.', '..']
    
    from elftools.common.py3compat import bytes2str
    from elftools.elf.elffile import ELFFile
    
    # 128k flash for the ATXmega128a4u
    flashsize = 128 * 1024
    
    
    def __printSectionInfo (s):
        print ('[{nr}] {name} {type} {addr} {offs} {size}'.format(
                    nr = s.header['sh_name'],
                    name = s.name,
                    type = s.header['sh_type'],
                    addr = s.header['sh_addr'],
                    offs = s.header['sh_offset'],
                    size = s.header['sh_size']
                                                                  )
               )
    
    def process_file(filename):
        print('In file: ' + filename)
        with open(filename, 'rb') as f:
            # get the data
            elffile = ELFFile(f)
            print ('sections:')
            for s in elffile.iter_sections():
                __printSectionInfo(s)
            print ('get the code from the .text section')
            textSec = elffile.get_section_by_name(b'.text')
            # prepare the memory
            flashMemory = bytearray(flashsize)
            # the text section
            startAddr = textSec.header['sh_addr']
            val = textSec.data()
    
            flashMemory[startAddr:startAddr+len(val)] = val
    
            # print memory
    
            print('finished')
    
    if __name__ == '__main__':
        process_file('firmware.elf')
    

    Tanks for the comments!