sigrok-fwextract-saleae-logic16: Add extraction of FX2 firmware
authorMarcus Comstedt <marcus@mc.pp.se>
Wed, 7 Aug 2013 22:33:27 +0000 (00:33 +0200)
committerUwe Hermann <uwe@hermann-uwe.de>
Thu, 8 Aug 2013 17:44:00 +0000 (19:44 +0200)
firmware/saleae-logic16/parseelf.py
firmware/saleae-logic16/sigrok-fwextract-saleae-logic16
firmware/saleae-logic16/sigrok-fwextract-saleae-logic16.1

index 1c6769e6acc24a94e4598f9c53721ec12834dae8..3bbbf84f36d1678a1593c3223065dbaa10bd129e 100644 (file)
@@ -68,6 +68,23 @@ class elf:
                                     ('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)
@@ -76,10 +93,20 @@ class elf:
             return {}
         strsec = this.read_section(strsechdr)
         this.file.seek(symsechdr['sh_offset'])
-        syms = [this.parse_symbol() for i in
+        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
@@ -126,6 +153,18 @@ class elf:
         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()
index 09b8730651b54a59ab1d52fbf6ac1ba29dd0b88e..45f98277c879a46d3d02fa1094a9551e64b6ab26 100755 (executable)
 ##
 
 import sys
+import struct
 import parseelf
 
+class searcher:
+
+    def reset(this, offs = 0):
+        if offs < 0 or offs > this.length:
+            raise Exception('Reset past end of section')
+        this.address = this.baseaddr + offs
+        this.offset = offs
+
+    def skip(this, cnt):
+        if this.offset + cnt > this.length:
+            raise Exception('Skip past end of section')
+        this.address += cnt
+        this.offset += cnt
+
+    def peek(this, cnt, offs=0):
+        if this.offset + offs + cnt > this.length:
+            raise Exception('Peek past end of section')
+        return this.data[this.offset + offs : this.offset + offs + cnt]
+
+    def look_for(this, needle):
+        pos = this.data.find(needle, this.offset)
+        if pos < 0:
+            raise Exception('Needle not found in haystack')
+        this.skip(pos - this.offset)
+
+    def __init__(this, data, addr):
+        this.data = data
+        this.baseaddr = addr
+        this.length = len(data)
+        this.reset()
+
+def search_plt_32(plt, addr):
+    plt.reset()
+    plt.look_for(struct.pack('<BBI', 0xff, 0x25, addr))        # jmp *addr32
+    return plt.address
+
+def search_plt_64(plt, addr):
+    plt.reset()
+    while True:
+        plt.look_for(b'\xff\x25')              # jmpq *offs32(%rip)
+        offs = struct.unpack('<i', plt.peek(4, 2))[0]
+        if plt.address + offs + 6 == addr:
+            return plt.address
+        plt.skip(2)
+
+def find_hex_file_lines_constructor_32(text, hex_file_lines_got, got_plt):
+    while True:
+        text.look_for(b'\x8b\xbb')             # mov offs32(%ebx),%edi
+        offs = struct.unpack('<i', text.peek(4, 2))[0]
+        if got_plt + offs == hex_file_lines_got:
+            text.skip(6)
+            return
+        text.skip(2)
+
+def find_hex_file_lines_constructor_64(text, hex_file_lines_got):
+    while True:
+        text.look_for(b'\x48\x8b\x2d')         # mov offs32(%rip),%rbp
+        offs = struct.unpack('<i', text.peek(4, 3))[0]
+        if text.address + offs + 7 == hex_file_lines_got:
+            text.skip(7)
+            return
+        text.skip(3)
+
+def parse_hex_file_lines_constructor_32(text, basic_string_plt, got_plt, lines):
+    cnt = len(lines)
+    while cnt > 0:
+        if text.peek(2) == b'\x8d\x45':                # lea offs8(%ebp),%eax
+            text.skip(3)
+        elif text.peek(2) == b'\x8d\x85':      # lea offs32(%ebp),%eax
+            text.skip(6)
+        if text.peek(1) == b'\xbe':            # mov $imm32,%esi
+            text.skip(5)
+        elif text.peek(2) == b'\x31\xf6':      # xor %esi,%esi
+            text.skip(2)
+        if text.peek(4) == b'\x89\x44\x24\x08':        # mov %eax,0x8(%esp)
+            text.skip(4)
+        if text.peek(2) == b'\x8d\x83':                # lea offs32(%ebx),%eax
+            straddr = struct.unpack('<i', text.peek(4, 2))[0]
+            text.skip(6)
+            straddr += got_plt
+        else:
+            raise Exception('Expected lea offs32(%ebx),%eax @ '+
+                            ('0x%x' % text.address))
+        if text.peek(4) == b'\x89\x44\x24\x04':        # mov %eax,0x4(%esp)
+            text.skip(4)
+        if text.peek(3) == b'\x89\x3c\x24':    # mov %edi,(%esp)
+            offs = 0
+            text.skip(3)
+        elif text.peek(2) == b'\x8d\x47':      # lea offs8(%edi),%eax
+            offs = struct.unpack('<b', text.peek(1, 2))[0]
+            text.skip(3)
+        elif text.peek(2) == b'\x8d\x87':      # lea offs32(%edi),%eax
+            offs = struct.unpack('<i', text.peek(4, 2))[0]
+            text.skip(6)
+        else:
+            raise Exception('Expected lea offs(%edi),%eax @ '+
+                            ('0x%x' % text.address))
+        if offs < 0 or offs > (len(lines) << 2) or (offs & 3) != 0:
+            raise Exception('Invalid offset %d' % offs)
+        index = offs >> 2
+        if lines[index] != 0:
+            raise Exception('Line %d filled multiple times' % index)
+        if text.peek(3) == b'\x89\x04\x24':    # mov %eax,(%esp)
+            text.skip(3)
+        if text.peek(1) == b'\xe8':            # call offs32
+            offs = struct.unpack('<i', text.peek(4, 1))[0]
+            text.skip(5)
+            if text.address + offs != basic_string_plt:
+                raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ '+
+                                ('0x%x' % text.address))
+        else:
+            raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ '+
+                            ('0x%x' % text.address))
+        if straddr == 0:
+            raise Exception('NULL pointer stored to index %d' % index)
+        lines[index] = straddr
+        cnt -= 1
+
+def parse_hex_file_lines_constructor_64(text, basic_string_plt, lines):
+    cnt = len(lines)
+    while cnt > 0:
+        if text.peek(1) == b'\xbb':            # mov $imm32,%ebx
+            text.skip(5)
+        elif text.peek(2) == b'\x31\xdb':      # xor %ebx,%ebx
+            text.skip(2)
+        if text.peek(4) == b'\x48\x8d\x54\x24':        # lea offs8(%rsp),%rdx
+            text.skip(5)
+        elif text.peek(4) == b'\x48\x8d\x94\x24': # lea offs32(%rsp),%rdx
+            text.skip(8)
+        if text.peek(3) == b'\x48\x8d\x35':    # lea offs32(%rip),%rsi
+            straddr = struct.unpack('<i', text.peek(4, 3))[0]
+            text.skip(7)
+            straddr += text.address
+        else:
+            raise Exception('Expected lea offs(%rip),%rsi @ '+
+                            ('0x%x' % text.address))
+        if text.peek(3) == b'\x48\x89\xef':    # mov %rbp,%rdi
+            offs = 0
+            text.skip(3)
+        elif text.peek(3) == b'\x48\x8d\x7d':  # lea offs8(%rbp),%rdi
+            offs = struct.unpack('<b', text.peek(1, 3))[0]
+            text.skip(4)
+        elif text.peek(3) == b'\x48\x8d\xbd':  # lea offs32(%rbp),%rdi
+            offs = struct.unpack('<i', text.peek(4, 3))[0]
+            text.skip(7)
+        else:
+            raise Exception('Expected lea offs(%rbp),%rdi @ '+
+                            ('0x%x' % text.address))
+        if offs < 0 or offs > (len(lines) << 3) or (offs & 7) != 0:
+            raise Exception('Invalid offset %d' % offs)
+        index = offs >> 3
+        if lines[index] != 0:
+            raise Exception('Line %d filled multiple times' % index)
+        if text.peek(1) == b'\xe8':            # callq offs32
+            offs = struct.unpack('<i', text.peek(4, 1))[0]
+            text.skip(5)
+            if text.address + offs != basic_string_plt:
+                raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ '+
+                                ('0x%x' % text.address))
+        else:
+            raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ '+
+                            ('0x%x' % text.address))
+        if straddr == 0:
+            raise Exception('NULL pointer stored to index %d' % index)
+        lines[index] = straddr
+        cnt -= 1
+
+def find_reloc(elf, symname):
+    for section, relocs in elf.relocs.items():
+        if 'symbols' in relocs and symname in relocs['symbols']:
+            symnum = relocs['symbols'][symname]['number']
+            for reloc in relocs['relocs']:
+                if reloc['r_sym'] == symnum:
+                    return reloc
+    raise Exception('Unable to find a relocation against '+symname)
+
+def ihex_to_binary(lines):
+    chunks = {}
+    for line in lines:
+        if line[0] != ':':
+            raise Exception('ihex line does not start with ":"')
+        line = bytes.fromhex(line[1:])
+        if (sum(bytearray(line)) & 0xff) != 0:
+            raise Exception('Invalid checksum in ihex')
+        (byte_count, address, rectype) = struct.unpack('>BHB', line[:4])
+        (data, checksum) = struct.unpack('>%dsB' % (byte_count), line[4:])
+        if rectype == 1 and byte_count == 0:
+            pass
+        elif rectype != 0 or byte_count == 0:
+            raise Exception('Unexpected rectype %d with bytecount %d' %
+                            (rectype, byte_count))
+        elif address in chunks:
+            raise Exception('Multiple ihex lines with address 0x%x' % address)
+        else:
+            chunks[address] = data
+    blob = b''
+    for address in sorted(iter(chunks)):
+        if address < len(blob):
+            raise Exception('Overlapping ihex chunks')
+        elif address > len(blob):
+            blob += b'\x00' * (address - len(blob))
+        blob += chunks[address]
+    return blob
+
+def extract_fx2_firmware(elf, symname, filename):
+    blob = elf.load_symbol(elf.dynsym[symname+'Count'])
+    count = struct.unpack('<I', blob)[0]
+    got_plt = elf.find_section('.got.plt')['sh_addr']
+    hex_file_lines_got = find_reloc(elf, symname)['r_offset'];
+    basic_string_got = find_reloc(elf, '_ZNSsC1EPKcRKSaIcE')['r_offset']
+    pltsec = elf.find_section('.plt')
+    plt = searcher(elf.read_section(pltsec), pltsec['sh_addr'])
+    try:
+        if elf.elf_wordsize == 64:
+            basic_string_plt = search_plt_64(plt, basic_string_got)
+        else:
+            basic_string_plt = search_plt_32(plt, basic_string_got)
+    except:
+        raise Exception('Unable to find a PLT entry for _ZNSsC1EPKcRKSaIcE')
+    textsec = elf.find_section('.text')
+    text = searcher(elf.read_section(textsec), textsec['sh_addr'])
+    while True:
+        try:
+            if elf.elf_wordsize == 64:
+                find_hex_file_lines_constructor_64(text, hex_file_lines_got)
+            else:
+                find_hex_file_lines_constructor_32(text, hex_file_lines_got,
+                                                   got_plt)
+        except:
+            raise Exception('Unable to find constructor for '+symname)
+        oldoffs = text.offset
+        l = [0]*count
+        try:
+            if elf.elf_wordsize == 64:
+                parse_hex_file_lines_constructor_64(text, basic_string_plt, l)
+            else:
+                parse_hex_file_lines_constructor_32(text, basic_string_plt,
+                                                    got_plt, l)
+            break
+        except KeyError:
+            text.reset(oldoffs)
+    rodatasec = elf.find_section('.rodata')
+    rodata = elf.read_section(rodatasec)
+    lo = rodatasec['sh_addr']
+    hi = lo + rodatasec['sh_size']
+    for i in range(count):
+        addr = l[i]
+        if addr < lo or addr >= hi:
+            raise Exception('Address 0x%x outside of .rodata section' % addr)
+        l[i] = elf.get_name(addr - lo, rodata)
+    blob = ihex_to_binary(l)
+    f = open(filename, 'wb')
+    f.write(blob)
+    f.close()
+    print("saved %d bytes to %s" % (len(blob), filename))
+
 def extract_symbol(elf, symname, filename):
     blob = elf.load_symbol(elf.dynsym[symname])
     f = open(filename, 'wb')
@@ -47,6 +304,9 @@ if len(sys.argv) != 2:
 try:
     filename = sys.argv[1]
     elf = parseelf.elf(filename)
+    if elf.ehdr['e_machine'] != 3 and elf.ehdr['e_machine'] != 62:
+        raise Exception('Unsupported e_machine')
+    extract_fx2_firmware(elf, 'gLogic16HexFileLines', 'saleae-logic16-fx2.fw')
     extract_bitstream(elf, '18')
     extract_bitstream(elf, '33')
 except Exception as e:
index 54ea43ac5adebdb57091efdadd99b6132b5d3949..3766602096ed4a036204dacc5006064d1f6d46f6 100644 (file)
@@ -4,14 +4,17 @@ sigrok\-fwextract\-saleae\-logic16 \- Extract Saleae Logic16 firmware
 .SH "SYNOPSIS"
 .B sigrok\-fwextract\-saleae\-logic16 [FILE]
 .SH "DESCRIPTION"
-This tool extracts FPGA bitstreams from the vendor software for the
-Saleae Logic16 USB logic analyzer. Download the Linux version (either 32-bit
-or 64-bit will do), and unpack it to find the main binary called "Logic".
+This tool extracts FX2 firmware and FPGA bitstreams from the vendor
+software for the Saleae Logic16 USB logic analyzer. Download the Linux
+version (either 32-bit or 64-bit will do), and unpack it to find the
+main binary called "Logic".
 .PP
 In order to extract the firmware/bitstreams, run the following command:
 .PP
 .B "  $ sigrok-fwextract-saleae-logic16 Logic"
 .br
+.RB "  saved 5214 bytes to saleae-logic16-fx2.fw"
+.br
 .RB "  saved 149516 bytes to saleae-logic16-fpga-18.bitstream"
 .br
 .RB "  saved 149516 bytes to saleae-logic16-fpga-33.bitstream"