]> sigrok.org Git - sigrok-util.git/blob - firmware/saleae-logic16/sigrok-fwextract-saleae-logic16
45f98277c879a46d3d02fa1094a9551e64b6ab26
[sigrok-util.git] / firmware / saleae-logic16 / sigrok-fwextract-saleae-logic16
1 #!/usr/bin/python3
2 ##
3 ## This file is part of the sigrok-util project.
4 ##
5 ## Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
6 ##
7 ## This program is free software; you can redistribute it and/or modify
8 ## it under the terms of the GNU General Public License as published by
9 ## the Free Software Foundation; either version 3 of the License, or
10 ## (at your option) any later version.
11 ##
12 ## This program is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ## GNU General Public License for more details.
16 ##
17 ## You should have received a copy of the GNU General Public License
18 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
19 ##
20
21 import sys
22 import struct
23 import parseelf
24
25 class searcher:
26
27     def reset(this, offs = 0):
28         if offs < 0 or offs > this.length:
29             raise Exception('Reset past end of section')
30         this.address = this.baseaddr + offs
31         this.offset = offs
32
33     def skip(this, cnt):
34         if this.offset + cnt > this.length:
35             raise Exception('Skip past end of section')
36         this.address += cnt
37         this.offset += cnt
38
39     def peek(this, cnt, offs=0):
40         if this.offset + offs + cnt > this.length:
41             raise Exception('Peek past end of section')
42         return this.data[this.offset + offs : this.offset + offs + cnt]
43
44     def look_for(this, needle):
45         pos = this.data.find(needle, this.offset)
46         if pos < 0:
47             raise Exception('Needle not found in haystack')
48         this.skip(pos - this.offset)
49
50     def __init__(this, data, addr):
51         this.data = data
52         this.baseaddr = addr
53         this.length = len(data)
54         this.reset()
55
56 def search_plt_32(plt, addr):
57     plt.reset()
58     plt.look_for(struct.pack('<BBI', 0xff, 0x25, addr)) # jmp *addr32
59     return plt.address
60
61 def search_plt_64(plt, addr):
62     plt.reset()
63     while True:
64         plt.look_for(b'\xff\x25')               # jmpq *offs32(%rip)
65         offs = struct.unpack('<i', plt.peek(4, 2))[0]
66         if plt.address + offs + 6 == addr:
67             return plt.address
68         plt.skip(2)
69
70 def find_hex_file_lines_constructor_32(text, hex_file_lines_got, got_plt):
71     while True:
72         text.look_for(b'\x8b\xbb')              # mov offs32(%ebx),%edi
73         offs = struct.unpack('<i', text.peek(4, 2))[0]
74         if got_plt + offs == hex_file_lines_got:
75             text.skip(6)
76             return
77         text.skip(2)
78
79 def find_hex_file_lines_constructor_64(text, hex_file_lines_got):
80     while True:
81         text.look_for(b'\x48\x8b\x2d')          # mov offs32(%rip),%rbp
82         offs = struct.unpack('<i', text.peek(4, 3))[0]
83         if text.address + offs + 7 == hex_file_lines_got:
84             text.skip(7)
85             return
86         text.skip(3)
87
88 def parse_hex_file_lines_constructor_32(text, basic_string_plt, got_plt, lines):
89     cnt = len(lines)
90     while cnt > 0:
91         if text.peek(2) == b'\x8d\x45':         # lea offs8(%ebp),%eax
92             text.skip(3)
93         elif text.peek(2) == b'\x8d\x85':       # lea offs32(%ebp),%eax
94             text.skip(6)
95         if text.peek(1) == b'\xbe':             # mov $imm32,%esi
96             text.skip(5)
97         elif text.peek(2) == b'\x31\xf6':       # xor %esi,%esi
98             text.skip(2)
99         if text.peek(4) == b'\x89\x44\x24\x08': # mov %eax,0x8(%esp)
100             text.skip(4)
101         if text.peek(2) == b'\x8d\x83':         # lea offs32(%ebx),%eax
102             straddr = struct.unpack('<i', text.peek(4, 2))[0]
103             text.skip(6)
104             straddr += got_plt
105         else:
106             raise Exception('Expected lea offs32(%ebx),%eax @ '+
107                             ('0x%x' % text.address))
108         if text.peek(4) == b'\x89\x44\x24\x04': # mov %eax,0x4(%esp)
109             text.skip(4)
110         if text.peek(3) == b'\x89\x3c\x24':     # mov %edi,(%esp)
111             offs = 0
112             text.skip(3)
113         elif text.peek(2) == b'\x8d\x47':       # lea offs8(%edi),%eax
114             offs = struct.unpack('<b', text.peek(1, 2))[0]
115             text.skip(3)
116         elif text.peek(2) == b'\x8d\x87':       # lea offs32(%edi),%eax
117             offs = struct.unpack('<i', text.peek(4, 2))[0]
118             text.skip(6)
119         else:
120             raise Exception('Expected lea offs(%edi),%eax @ '+
121                             ('0x%x' % text.address))
122         if offs < 0 or offs > (len(lines) << 2) or (offs & 3) != 0:
123             raise Exception('Invalid offset %d' % offs)
124         index = offs >> 2
125         if lines[index] != 0:
126             raise Exception('Line %d filled multiple times' % index)
127         if text.peek(3) == b'\x89\x04\x24':     # mov %eax,(%esp)
128             text.skip(3)
129         if text.peek(1) == b'\xe8':             # call offs32
130             offs = struct.unpack('<i', text.peek(4, 1))[0]
131             text.skip(5)
132             if text.address + offs != basic_string_plt:
133                 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ '+
134                                 ('0x%x' % text.address))
135         else:
136             raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ '+
137                             ('0x%x' % text.address))
138         if straddr == 0:
139             raise Exception('NULL pointer stored to index %d' % index)
140         lines[index] = straddr
141         cnt -= 1
142
143 def parse_hex_file_lines_constructor_64(text, basic_string_plt, lines):
144     cnt = len(lines)
145     while cnt > 0:
146         if text.peek(1) == b'\xbb':             # mov $imm32,%ebx
147             text.skip(5)
148         elif text.peek(2) == b'\x31\xdb':       # xor %ebx,%ebx
149             text.skip(2)
150         if text.peek(4) == b'\x48\x8d\x54\x24': # lea offs8(%rsp),%rdx
151             text.skip(5)
152         elif text.peek(4) == b'\x48\x8d\x94\x24': # lea offs32(%rsp),%rdx
153             text.skip(8)
154         if text.peek(3) == b'\x48\x8d\x35':     # lea offs32(%rip),%rsi
155             straddr = struct.unpack('<i', text.peek(4, 3))[0]
156             text.skip(7)
157             straddr += text.address
158         else:
159             raise Exception('Expected lea offs(%rip),%rsi @ '+
160                             ('0x%x' % text.address))
161         if text.peek(3) == b'\x48\x89\xef':     # mov %rbp,%rdi
162             offs = 0
163             text.skip(3)
164         elif text.peek(3) == b'\x48\x8d\x7d':   # lea offs8(%rbp),%rdi
165             offs = struct.unpack('<b', text.peek(1, 3))[0]
166             text.skip(4)
167         elif text.peek(3) == b'\x48\x8d\xbd':   # lea offs32(%rbp),%rdi
168             offs = struct.unpack('<i', text.peek(4, 3))[0]
169             text.skip(7)
170         else:
171             raise Exception('Expected lea offs(%rbp),%rdi @ '+
172                             ('0x%x' % text.address))
173         if offs < 0 or offs > (len(lines) << 3) or (offs & 7) != 0:
174             raise Exception('Invalid offset %d' % offs)
175         index = offs >> 3
176         if lines[index] != 0:
177             raise Exception('Line %d filled multiple times' % index)
178         if text.peek(1) == b'\xe8':             # callq offs32
179             offs = struct.unpack('<i', text.peek(4, 1))[0]
180             text.skip(5)
181             if text.address + offs != basic_string_plt:
182                 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ '+
183                                 ('0x%x' % text.address))
184         else:
185             raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ '+
186                             ('0x%x' % text.address))
187         if straddr == 0:
188             raise Exception('NULL pointer stored to index %d' % index)
189         lines[index] = straddr
190         cnt -= 1
191
192 def find_reloc(elf, symname):
193     for section, relocs in elf.relocs.items():
194         if 'symbols' in relocs and symname in relocs['symbols']:
195             symnum = relocs['symbols'][symname]['number']
196             for reloc in relocs['relocs']:
197                 if reloc['r_sym'] == symnum:
198                     return reloc
199     raise Exception('Unable to find a relocation against '+symname)
200
201 def ihex_to_binary(lines):
202     chunks = {}
203     for line in lines:
204         if line[0] != ':':
205             raise Exception('ihex line does not start with ":"')
206         line = bytes.fromhex(line[1:])
207         if (sum(bytearray(line)) & 0xff) != 0:
208             raise Exception('Invalid checksum in ihex')
209         (byte_count, address, rectype) = struct.unpack('>BHB', line[:4])
210         (data, checksum) = struct.unpack('>%dsB' % (byte_count), line[4:])
211         if rectype == 1 and byte_count == 0:
212             pass
213         elif rectype != 0 or byte_count == 0:
214             raise Exception('Unexpected rectype %d with bytecount %d' %
215                             (rectype, byte_count))
216         elif address in chunks:
217             raise Exception('Multiple ihex lines with address 0x%x' % address)
218         else:
219             chunks[address] = data
220     blob = b''
221     for address in sorted(iter(chunks)):
222         if address < len(blob):
223             raise Exception('Overlapping ihex chunks')
224         elif address > len(blob):
225             blob += b'\x00' * (address - len(blob))
226         blob += chunks[address]
227     return blob
228
229 def extract_fx2_firmware(elf, symname, filename):
230     blob = elf.load_symbol(elf.dynsym[symname+'Count'])
231     count = struct.unpack('<I', blob)[0]
232     got_plt = elf.find_section('.got.plt')['sh_addr']
233     hex_file_lines_got = find_reloc(elf, symname)['r_offset'];
234     basic_string_got = find_reloc(elf, '_ZNSsC1EPKcRKSaIcE')['r_offset']
235     pltsec = elf.find_section('.plt')
236     plt = searcher(elf.read_section(pltsec), pltsec['sh_addr'])
237     try:
238         if elf.elf_wordsize == 64:
239             basic_string_plt = search_plt_64(plt, basic_string_got)
240         else:
241             basic_string_plt = search_plt_32(plt, basic_string_got)
242     except:
243         raise Exception('Unable to find a PLT entry for _ZNSsC1EPKcRKSaIcE')
244     textsec = elf.find_section('.text')
245     text = searcher(elf.read_section(textsec), textsec['sh_addr'])
246     while True:
247         try:
248             if elf.elf_wordsize == 64:
249                 find_hex_file_lines_constructor_64(text, hex_file_lines_got)
250             else:
251                 find_hex_file_lines_constructor_32(text, hex_file_lines_got,
252                                                    got_plt)
253         except:
254             raise Exception('Unable to find constructor for '+symname)
255         oldoffs = text.offset
256         l = [0]*count
257         try:
258             if elf.elf_wordsize == 64:
259                 parse_hex_file_lines_constructor_64(text, basic_string_plt, l)
260             else:
261                 parse_hex_file_lines_constructor_32(text, basic_string_plt,
262                                                     got_plt, l)
263             break
264         except KeyError:
265             text.reset(oldoffs)
266     rodatasec = elf.find_section('.rodata')
267     rodata = elf.read_section(rodatasec)
268     lo = rodatasec['sh_addr']
269     hi = lo + rodatasec['sh_size']
270     for i in range(count):
271         addr = l[i]
272         if addr < lo or addr >= hi:
273             raise Exception('Address 0x%x outside of .rodata section' % addr)
274         l[i] = elf.get_name(addr - lo, rodata)
275     blob = ihex_to_binary(l)
276     f = open(filename, 'wb')
277     f.write(blob)
278     f.close()
279     print("saved %d bytes to %s" % (len(blob), filename))
280
281 def extract_symbol(elf, symname, filename):
282     blob = elf.load_symbol(elf.dynsym[symname])
283     f = open(filename, 'wb')
284     f.write(blob)
285     f.close()
286     print("saved %d bytes to %s" % (len(blob), filename))
287
288 def extract_bitstream(elf, lv):
289     extract_symbol(elf, 'gLogic16Lv'+lv+'CompressedBitstream',
290                    'saleae-logic16-fpga-'+lv+'.bitstream')
291
292 def usage():
293     print("sigrok-fwextract-saleae-logic16 <programfile>")
294     sys.exit()
295
296
297 #
298 # main
299 #
300
301 if len(sys.argv) != 2:
302     usage()
303
304 try:
305     filename = sys.argv[1]
306     elf = parseelf.elf(filename)
307     if elf.ehdr['e_machine'] != 3 and elf.ehdr['e_machine'] != 62:
308         raise Exception('Unsupported e_machine')
309     extract_fx2_firmware(elf, 'gLogic16HexFileLines', 'saleae-logic16-fx2.fw')
310     extract_bitstream(elf, '18')
311     extract_bitstream(elf, '33')
312 except Exception as e:
313     print("Error: %s" % str(e))
314