Move/rename firmware extractor tools.
authorUwe Hermann <uwe@hermann-uwe.de>
Mon, 5 Aug 2013 12:44:53 +0000 (14:44 +0200)
committerUwe Hermann <uwe@hermann-uwe.de>
Mon, 5 Aug 2013 12:55:05 +0000 (14:55 +0200)
Since sigrok-util will be released as tarball with the usual build
system at some point, and the tools will be installed in /usr/bin by
most distros, the tools need to have unique names as to not conflict
with other things in /usr/bin. We use a common 'sigrok-fwextract-' prefix
for all current and future firmware extractor tools to keep the
names consistent and unique.

Drop the .py suffix from the executables, since the implementation
language doesn't matter and is generally not encoded in the filename
in things installed in /usr/bin.

Later on, every tool will also get a manpage, of course.

14 files changed:
firmware/README.hantek-dso-extract [deleted file]
firmware/README.parsepe [deleted file]
firmware/README.saleae-logic16-extract [deleted file]
firmware/hantek-dso-extract.py [deleted file]
firmware/hantek-dso/README.parsepe [new file with mode: 0644]
firmware/hantek-dso/README.sigrok-fwextract-hantek-dso [new file with mode: 0644]
firmware/hantek-dso/parsepe.py [new file with mode: 0755]
firmware/hantek-dso/sigrok-fwextract-hantek-dso [new file with mode: 0755]
firmware/parseelf.py [deleted file]
firmware/parsepe.py [deleted file]
firmware/saleae-logic16-extract.py [deleted file]
firmware/saleae-logic16/README.sigrok-fwextract-saleae-logic16 [new file with mode: 0644]
firmware/saleae-logic16/parseelf.py [new file with mode: 0644]
firmware/saleae-logic16/sigrok-fwextract-saleae-logic16 [new file with mode: 0755]

diff --git a/firmware/README.hantek-dso-extract b/firmware/README.hantek-dso-extract
deleted file mode 100644 (file)
index f0a765a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-The hantek-dso-extract.py tool extracts firmware from the driver that comes
-with the Hantek DSO-2xxx/52xx series USB oscilloscopes. Find the 32-bit
-driver installed on the windows system -- typically called DSOxxxx1.sys or
-DsoxxxxX861.sys, where xxxx is your device's model. Use it like this:
-
-$ ./hantek-dso-extract.py Dso2090X861.sys
-saved 4730 bytes to hantek-dso-2090.fw
-
-Copy the resulting file over to the location where sigrok installed its
-firmware files. By default this is /usr/local/share/sigrok-firmware
diff --git a/firmware/README.parsepe b/firmware/README.parsepe
deleted file mode 100644 (file)
index 8a69b6f..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-The parsepe.py tool can list all sections and symbols from a PE (portable
-executable) binary file, and extract the contents of a symbol.
-
-usage:
-parsepe.py -l <filename>                list all sections and symbols in file
-parsepe.py -s <symbol> -x <filename>    extract symbol from file
-
-TODO:
-- currently only handles COFF symbol tables
-- can only extract external (global) symbols
-
diff --git a/firmware/README.saleae-logic16-extract b/firmware/README.saleae-logic16-extract
deleted file mode 100644 (file)
index ad7d355..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-The saleae-logic16-extract.py 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".
-
-Usage:
-
-$ ./saleae-logic16-extract.py Logic
-saved 149516 bytes to saleae-logic16-fpga-18.bitstream
-saved 149516 bytes to saleae-logic16-fpga-33.bitstream
-
-Copy the resulting files over to the location where sigrok installed its
-firmware files. By default this is /usr/local/share/sigrok-firmware.
diff --git a/firmware/hantek-dso-extract.py b/firmware/hantek-dso-extract.py
deleted file mode 100755 (executable)
index 045e50a..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/usr/bin/python3
-##
-## This file is part of the sigrok-util project.
-##
-## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
-##
-## 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 sys
-import os
-import re
-import struct
-from array import array
-
-import parsepe
-
-
-def find_model(filename):
-       filename = os.path.split(filename)[-1]
-       m = re.search('^dso([a-z0-9]+)1.sys$', filename, re.I)
-       if m:
-               model = m.group(1).upper()
-               model = model.replace('X86', '').replace('AMD64', '').replace('IA64', '')
-               if model == '520A':
-                       model = '5200A'
-       else:
-               model = 'unknown'
-
-       return model
-
-
-def unsparse(data):
-       p = 0
-       maxaddr = 0
-       blob = array('B', [0] * 0x4000)
-       while p <= len(data) and data[p+4] == 0:
-               num_bytes = struct.unpack("<H", data[p:p+2])[0]
-               address = struct.unpack("<H", data[p+2:p+4])[0]
-               chunk = array('B')
-               chunk.frombytes(data[p+5:p+5+num_bytes])
-               p += 22
-
-               if address > 0x4000:
-                       # the FX2 only has 16K RAM. other writes are to registers
-                       # in the 0xe000 region, skip those
-                       continue
-
-               blob[address:address+num_bytes] = chunk
-
-               if address + num_bytes > maxaddr:
-                       maxaddr = address + num_bytes
-
-       return blob[:maxaddr].tostring()
-
-
-def usage():
-       print("hantek-dso-extract.py <driverfile>")
-       sys.exit()
-
-
-#
-# main
-#
-
-if len(sys.argv) != 2:
-       usage()
-
-try:
-       filename = sys.argv[1]
-       binihx = parsepe.extract_symbol(filename, '_firmware')
-       if binihx is None:
-               raise Exception("no firmware found")
-       blob = unsparse(binihx)
-       outfile = 'hantek-dso-' + find_model(filename) + '.fw'
-       open(outfile, 'wb').write(blob)
-       print("saved %d bytes to %s" % (len(blob), outfile))
-except Exception as e:
-       print("Error: %s" % str(e))
-
-
-
diff --git a/firmware/hantek-dso/README.parsepe b/firmware/hantek-dso/README.parsepe
new file mode 100644 (file)
index 0000000..8a69b6f
--- /dev/null
@@ -0,0 +1,11 @@
+The parsepe.py tool can list all sections and symbols from a PE (portable
+executable) binary file, and extract the contents of a symbol.
+
+usage:
+parsepe.py -l <filename>                list all sections and symbols in file
+parsepe.py -s <symbol> -x <filename>    extract symbol from file
+
+TODO:
+- currently only handles COFF symbol tables
+- can only extract external (global) symbols
+
diff --git a/firmware/hantek-dso/README.sigrok-fwextract-hantek-dso b/firmware/hantek-dso/README.sigrok-fwextract-hantek-dso
new file mode 100644 (file)
index 0000000..4dc7d48
--- /dev/null
@@ -0,0 +1,10 @@
+This tool extracts firmware from the driver that comes
+with the Hantek DSO-2xxx/52xx series USB oscilloscopes. Find the 32-bit
+driver installed on the windows system -- typically called DSOxxxx1.sys or
+DsoxxxxX861.sys, where xxxx is your device's model. Use it like this:
+
+$ sigrok-fwextract-hantek-dso Dso2090X861.sys
+saved 4730 bytes to hantek-dso-2090.fw
+
+Copy the resulting file over to the location where sigrok installed its
+firmware files. By default this is /usr/local/share/sigrok-firmware.
diff --git a/firmware/hantek-dso/parsepe.py b/firmware/hantek-dso/parsepe.py
new file mode 100755 (executable)
index 0000000..7d3fd0e
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/python3
+##
+## This file is part of the sigrok-util project.
+##
+## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
+##
+## 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 sys
+import os
+from getopt import getopt
+import struct
+
+
+def parse(filename):
+       f = open(filename, 'rb')
+       if f.read(2) != b'MZ':
+               raise Exception("MZ signature not found.")
+
+       sections = []
+       # long e_lfanew
+       f.seek(0x3c)
+       pe_ptr = struct.unpack("<L", f.read(4))[0]
+       f.seek(pe_ptr)
+       if f.read(4) != b'\x50\x45\x00\x00':
+               raise Exception("PE signature not found.")
+       # skip Machine
+       f.seek(f.tell() + 2)
+       sections.append(['header', 324, 0])
+       num_sections = struct.unpack("<H", f.read(2))[0]
+       # skip TimeDateStamp
+       f.seek(f.tell() + 4)
+       symboltable_address = struct.unpack("<L", f.read(4))[0]
+       num_symbols = struct.unpack("<L", f.read(4))[0]
+       optheader_size = struct.unpack("<H", f.read(2))[0]
+       # skip past PE header and PE optional header
+       f.seek(f.tell() + 2 + optheader_size)
+
+       for i in range(num_sections):
+               name = f.read(8).decode('ascii', errors='ignore').strip('\x00')
+               # skip past Misc and VirtualAddress
+               f.seek(f.tell() + 8)
+               # SizeOfRawData
+               size = struct.unpack("<L", f.read(4))[0]
+               # PointerToRawData
+               ptr = struct.unpack("<L", f.read(4))[0]
+               # skip to next section header
+               f.seek(f.tell() + 16)
+               sections.append([name, size, ptr])
+
+       symbols = []
+       addr = symboltable_address
+       stringtable_address = symboltable_address + num_symbols * 18
+       for i in range(num_symbols):
+               f.seek(addr)
+               tmp = f.read(8)
+               symaddr = struct.unpack("<L", f.read(4))[0]
+               # skip SectionNumber and Type
+               symtype = struct.unpack("B", f.read(1))[0]
+               f.seek(f.tell() + 4)
+               if tmp[:4] == b'\x00\x00\x00\x00':
+                       # symbol name is in the string table
+                       straddr = stringtable_address + struct.unpack("<l", tmp[4:])[0]
+                       f.seek(straddr)
+                       tmpname = f.read(64)
+                       name = tmpname[:tmpname.find(b'\x00')]
+               else:
+                       name = tmp
+               name = name.decode('ascii', errors='ignore').strip('\x00')
+               # need IMAGE_SYM_CLASS_EXTERNAL
+               if symtype == 0x02:
+                       size = 0
+               else:
+                       size = None
+               if i != 0 and symbols[-1][2] is not None and symaddr > symbols[-1][1]:
+                       symbols[-1][2] = symaddr - symbols[-1][1]
+               symbols.append([name, symaddr, size])
+               addr += 18
+
+       f.close()
+
+       return sections, symbols
+
+
+def list_all(filename):
+       sections, symbols = parse(filename)
+       if sections:
+               print("Sections:\n    Name         Size\t  Position")
+               cnt = 0
+               for name, size, address in sections:
+                       print("%-3d %-8s    %5d\t  0x%.8x" % (cnt, name, size, address))
+                       cnt += 1
+       if symbols:
+               print("\nSymbol table:\n   Address     Size    Symbol")
+               for symbol, address, size in symbols:
+                       if size is not None:
+                               sizestr = "%5d" % size
+                       else:
+                               sizestr = "     "
+                       print("0x%.8x  %s    %-8s" % (address, sizestr, symbol))
+               print()
+
+
+def extract_symbol(filename, symbol):
+       sections, symbols = parse(filename)
+       if not symbols:
+               return None
+       data = None
+       for symbolname, address, size in symbols:
+               if symbolname == symbol:
+                       if size is None:
+                               raise Exception("symbol %s found, but has unknown size")
+                       f = open(filename, 'rb')
+                       f.seek(address)
+                       data = f.read(size)
+                       f.close()
+                       if len(data) != size:
+                               raise Exception("short file")
+                       break
+
+       if data is None:
+               raise Exception("symbol %s not found" % symbol)
+
+       return data
+
+
+
+def usage():
+       print("usage: parsepe.py [-s <symbol>] <-l|-x> <filename>")
+       print("  -l   list all sections and symbols in file")
+       print("  -x   extract symbol from file (specify symbol name with -s)")
+       sys.exit()
+
+
+#
+# main
+#
+
+if __name__ == '__main__':
+       filename = symbol = mode = None
+       opts, args = getopt(sys.argv[1:], 's:lx')
+       for opt, arg in opts:
+               if opt == '-s':
+                       symbol = arg
+               elif opt == '-l':
+                       mode = 'list'
+               elif opt == '-x':
+                       mode = 'extract'
+
+       if len(args) != 1:
+               usage()
+       if mode is None and symbol is None:
+               usage()
+
+       try:
+               filename = args[0]
+               if mode == 'list':
+                       list_all(filename)
+               elif mode == 'extract':
+                       if symbol is None:
+                               raise Exception("specify a symbol to extract")
+                       data = extract_symbol(filename, symbol)
+                       outfile = os.path.splitext(filename)[0] + symbol
+                       open(outfile, 'wb').write(data)
+                       print("saved %d bytes to %s" % (len(data), outfile))
+               else:
+                       raise Exception("specify -l or -x")
+       except Exception as e:
+               print("Error: %s" % str(e))
+
+
diff --git a/firmware/hantek-dso/sigrok-fwextract-hantek-dso b/firmware/hantek-dso/sigrok-fwextract-hantek-dso
new file mode 100755 (executable)
index 0000000..c90e36f
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/python3
+##
+## This file is part of the sigrok-util project.
+##
+## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
+##
+## 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 sys
+import os
+import re
+import struct
+from array import array
+
+import parsepe
+
+
+def find_model(filename):
+       filename = os.path.split(filename)[-1]
+       m = re.search('^dso([a-z0-9]+)1.sys$', filename, re.I)
+       if m:
+               model = m.group(1).upper()
+               model = model.replace('X86', '').replace('AMD64', '').replace('IA64', '')
+               if model == '520A':
+                       model = '5200A'
+       else:
+               model = 'unknown'
+
+       return model
+
+
+def unsparse(data):
+       p = 0
+       maxaddr = 0
+       blob = array('B', [0] * 0x4000)
+       while p <= len(data) and data[p+4] == 0:
+               num_bytes = struct.unpack("<H", data[p:p+2])[0]
+               address = struct.unpack("<H", data[p+2:p+4])[0]
+               chunk = array('B')
+               chunk.frombytes(data[p+5:p+5+num_bytes])
+               p += 22
+
+               if address > 0x4000:
+                       # the FX2 only has 16K RAM. other writes are to registers
+                       # in the 0xe000 region, skip those
+                       continue
+
+               blob[address:address+num_bytes] = chunk
+
+               if address + num_bytes > maxaddr:
+                       maxaddr = address + num_bytes
+
+       return blob[:maxaddr].tostring()
+
+
+def usage():
+       print("sigrok-fwextract-hantek-dso <driverfile>")
+       sys.exit()
+
+
+#
+# main
+#
+
+if len(sys.argv) != 2:
+       usage()
+
+try:
+       filename = sys.argv[1]
+       binihx = parsepe.extract_symbol(filename, '_firmware')
+       if binihx is None:
+               raise Exception("no firmware found")
+       blob = unsparse(binihx)
+       outfile = 'hantek-dso-' + find_model(filename) + '.fw'
+       open(outfile, 'wb').write(blob)
+       print("saved %d bytes to %s" % (len(blob), outfile))
+except Exception as e:
+       print("Error: %s" % str(e))
+
+
+
diff --git a/firmware/parseelf.py b/firmware/parseelf.py
deleted file mode 100644 (file)
index 1c6769e..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/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_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 = [this.parse_symbol() 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 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')
-
-    def __del__(this):
-        try:
-            this.file.close()
-        except AttributeError:
-            pass
diff --git a/firmware/parsepe.py b/firmware/parsepe.py
deleted file mode 100755 (executable)
index 7d3fd0e..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/usr/bin/python3
-##
-## This file is part of the sigrok-util project.
-##
-## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
-##
-## 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 sys
-import os
-from getopt import getopt
-import struct
-
-
-def parse(filename):
-       f = open(filename, 'rb')
-       if f.read(2) != b'MZ':
-               raise Exception("MZ signature not found.")
-
-       sections = []
-       # long e_lfanew
-       f.seek(0x3c)
-       pe_ptr = struct.unpack("<L", f.read(4))[0]
-       f.seek(pe_ptr)
-       if f.read(4) != b'\x50\x45\x00\x00':
-               raise Exception("PE signature not found.")
-       # skip Machine
-       f.seek(f.tell() + 2)
-       sections.append(['header', 324, 0])
-       num_sections = struct.unpack("<H", f.read(2))[0]
-       # skip TimeDateStamp
-       f.seek(f.tell() + 4)
-       symboltable_address = struct.unpack("<L", f.read(4))[0]
-       num_symbols = struct.unpack("<L", f.read(4))[0]
-       optheader_size = struct.unpack("<H", f.read(2))[0]
-       # skip past PE header and PE optional header
-       f.seek(f.tell() + 2 + optheader_size)
-
-       for i in range(num_sections):
-               name = f.read(8).decode('ascii', errors='ignore').strip('\x00')
-               # skip past Misc and VirtualAddress
-               f.seek(f.tell() + 8)
-               # SizeOfRawData
-               size = struct.unpack("<L", f.read(4))[0]
-               # PointerToRawData
-               ptr = struct.unpack("<L", f.read(4))[0]
-               # skip to next section header
-               f.seek(f.tell() + 16)
-               sections.append([name, size, ptr])
-
-       symbols = []
-       addr = symboltable_address
-       stringtable_address = symboltable_address + num_symbols * 18
-       for i in range(num_symbols):
-               f.seek(addr)
-               tmp = f.read(8)
-               symaddr = struct.unpack("<L", f.read(4))[0]
-               # skip SectionNumber and Type
-               symtype = struct.unpack("B", f.read(1))[0]
-               f.seek(f.tell() + 4)
-               if tmp[:4] == b'\x00\x00\x00\x00':
-                       # symbol name is in the string table
-                       straddr = stringtable_address + struct.unpack("<l", tmp[4:])[0]
-                       f.seek(straddr)
-                       tmpname = f.read(64)
-                       name = tmpname[:tmpname.find(b'\x00')]
-               else:
-                       name = tmp
-               name = name.decode('ascii', errors='ignore').strip('\x00')
-               # need IMAGE_SYM_CLASS_EXTERNAL
-               if symtype == 0x02:
-                       size = 0
-               else:
-                       size = None
-               if i != 0 and symbols[-1][2] is not None and symaddr > symbols[-1][1]:
-                       symbols[-1][2] = symaddr - symbols[-1][1]
-               symbols.append([name, symaddr, size])
-               addr += 18
-
-       f.close()
-
-       return sections, symbols
-
-
-def list_all(filename):
-       sections, symbols = parse(filename)
-       if sections:
-               print("Sections:\n    Name         Size\t  Position")
-               cnt = 0
-               for name, size, address in sections:
-                       print("%-3d %-8s    %5d\t  0x%.8x" % (cnt, name, size, address))
-                       cnt += 1
-       if symbols:
-               print("\nSymbol table:\n   Address     Size    Symbol")
-               for symbol, address, size in symbols:
-                       if size is not None:
-                               sizestr = "%5d" % size
-                       else:
-                               sizestr = "     "
-                       print("0x%.8x  %s    %-8s" % (address, sizestr, symbol))
-               print()
-
-
-def extract_symbol(filename, symbol):
-       sections, symbols = parse(filename)
-       if not symbols:
-               return None
-       data = None
-       for symbolname, address, size in symbols:
-               if symbolname == symbol:
-                       if size is None:
-                               raise Exception("symbol %s found, but has unknown size")
-                       f = open(filename, 'rb')
-                       f.seek(address)
-                       data = f.read(size)
-                       f.close()
-                       if len(data) != size:
-                               raise Exception("short file")
-                       break
-
-       if data is None:
-               raise Exception("symbol %s not found" % symbol)
-
-       return data
-
-
-
-def usage():
-       print("usage: parsepe.py [-s <symbol>] <-l|-x> <filename>")
-       print("  -l   list all sections and symbols in file")
-       print("  -x   extract symbol from file (specify symbol name with -s)")
-       sys.exit()
-
-
-#
-# main
-#
-
-if __name__ == '__main__':
-       filename = symbol = mode = None
-       opts, args = getopt(sys.argv[1:], 's:lx')
-       for opt, arg in opts:
-               if opt == '-s':
-                       symbol = arg
-               elif opt == '-l':
-                       mode = 'list'
-               elif opt == '-x':
-                       mode = 'extract'
-
-       if len(args) != 1:
-               usage()
-       if mode is None and symbol is None:
-               usage()
-
-       try:
-               filename = args[0]
-               if mode == 'list':
-                       list_all(filename)
-               elif mode == 'extract':
-                       if symbol is None:
-                               raise Exception("specify a symbol to extract")
-                       data = extract_symbol(filename, symbol)
-                       outfile = os.path.splitext(filename)[0] + symbol
-                       open(outfile, 'wb').write(data)
-                       print("saved %d bytes to %s" % (len(data), outfile))
-               else:
-                       raise Exception("specify -l or -x")
-       except Exception as e:
-               print("Error: %s" % str(e))
-
-
diff --git a/firmware/saleae-logic16-extract.py b/firmware/saleae-logic16-extract.py
deleted file mode 100755 (executable)
index c325174..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/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 sys
-import parseelf
-
-def extract_symbol(elf, symname, filename):
-    blob = elf.load_symbol(elf.dynsym[symname])
-    f = open(filename, 'wb')
-    f.write(blob)
-    f.close()
-    print("saved %d bytes to %s" % (len(blob), filename))
-
-def extract_bitstream(elf, lv):
-    extract_symbol(elf, 'gLogic16Lv'+lv+'CompressedBitstream',
-                   'saleae-logic16-fpga-'+lv+'.bitstream')
-
-def usage():
-    print("saleae-logic16-extract.py <programfile>")
-    sys.exit()
-
-
-#
-# main
-#
-
-if len(sys.argv) != 2:
-    usage()
-
-try:
-    filename = sys.argv[1]
-    elf = parseelf.elf(filename)
-    extract_bitstream(elf, '18')
-    extract_bitstream(elf, '33')
-except Exception as e:
-    print("Error: %s" % str(e))
-
diff --git a/firmware/saleae-logic16/README.sigrok-fwextract-saleae-logic16 b/firmware/saleae-logic16/README.sigrok-fwextract-saleae-logic16
new file mode 100644 (file)
index 0000000..4f7fb3b
--- /dev/null
@@ -0,0 +1,13 @@
+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".
+
+Usage:
+
+$ ./sigrok-fwextract-saleae-logic16 Logic
+saved 149516 bytes to saleae-logic16-fpga-18.bitstream
+saved 149516 bytes to saleae-logic16-fpga-33.bitstream
+
+Copy the resulting files over to the location where sigrok installed its
+firmware files. By default this is /usr/local/share/sigrok-firmware.
diff --git a/firmware/saleae-logic16/parseelf.py b/firmware/saleae-logic16/parseelf.py
new file mode 100644 (file)
index 0000000..1c6769e
--- /dev/null
@@ -0,0 +1,133 @@
+#!/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_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 = [this.parse_symbol() 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 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')
+
+    def __del__(this):
+        try:
+            this.file.close()
+        except AttributeError:
+            pass
diff --git a/firmware/saleae-logic16/sigrok-fwextract-saleae-logic16 b/firmware/saleae-logic16/sigrok-fwextract-saleae-logic16
new file mode 100755 (executable)
index 0000000..09b8730
--- /dev/null
@@ -0,0 +1,54 @@
+#!/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 sys
+import parseelf
+
+def extract_symbol(elf, symname, filename):
+    blob = elf.load_symbol(elf.dynsym[symname])
+    f = open(filename, 'wb')
+    f.write(blob)
+    f.close()
+    print("saved %d bytes to %s" % (len(blob), filename))
+
+def extract_bitstream(elf, lv):
+    extract_symbol(elf, 'gLogic16Lv'+lv+'CompressedBitstream',
+                   'saleae-logic16-fpga-'+lv+'.bitstream')
+
+def usage():
+    print("sigrok-fwextract-saleae-logic16 <programfile>")
+    sys.exit()
+
+
+#
+# main
+#
+
+if len(sys.argv) != 2:
+    usage()
+
+try:
+    filename = sys.argv[1]
+    elf = parseelf.elf(filename)
+    extract_bitstream(elf, '18')
+    extract_bitstream(elf, '33')
+except Exception as e:
+    print("Error: %s" % str(e))
+