]> sigrok.org Git - sigrok-util.git/blame - firmware/saleae-logic16/sigrok-fwextract-saleae-logic16
sigrok-fwextract-saleae-logic16: Add extraction of Logic Pro firmware
[sigrok-util.git] / firmware / saleae-logic16 / sigrok-fwextract-saleae-logic16
CommitLineData
a5419fb5
MC
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
21import sys
1b0c6828 22import struct
a5419fb5
MC
23import parseelf
24
1b0c6828
MC
25class searcher:
26
08a979af 27 def reset(this, offs=0):
1b0c6828
MC
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
beb1bd5a
MC
50 def look_for_either(this, needle1, needle2):
51 pos1 = this.data.find(needle1, this.offset)
52 pos2 = this.data.find(needle2, this.offset)
53 if pos1 < 0 and pos2 < 0:
54 raise Exception('Needle not found in haystack')
55 if pos1 < 0 or pos2 < pos1:
56 pos1 = pos2
57 this.skip(pos1 - this.offset)
58
1b0c6828
MC
59 def __init__(this, data, addr):
60 this.data = data
61 this.baseaddr = addr
62 this.length = len(data)
63 this.reset()
64
65def search_plt_32(plt, addr):
66 plt.reset()
67 plt.look_for(struct.pack('<BBI', 0xff, 0x25, addr)) # jmp *addr32
68 return plt.address
69
70def search_plt_64(plt, addr):
71 plt.reset()
72 while True:
73 plt.look_for(b'\xff\x25') # jmpq *offs32(%rip)
74 offs = struct.unpack('<i', plt.peek(4, 2))[0]
75 if plt.address + offs + 6 == addr:
76 return plt.address
77 plt.skip(2)
78
79def find_hex_file_lines_constructor_32(text, hex_file_lines_got, got_plt):
80 while True:
beb1bd5a 81 text.look_for_either(b'\x8b\xbb', b'\x8b\xb3') # mov offs32(%ebx),{%edi,%esi}
1b0c6828
MC
82 offs = struct.unpack('<i', text.peek(4, 2))[0]
83 if got_plt + offs == hex_file_lines_got:
84 text.skip(6)
85 return
86 text.skip(2)
87
88def find_hex_file_lines_constructor_64(text, hex_file_lines_got):
89 while True:
90 text.look_for(b'\x48\x8b\x2d') # mov offs32(%rip),%rbp
91 offs = struct.unpack('<i', text.peek(4, 3))[0]
92 if text.address + offs + 7 == hex_file_lines_got:
93 text.skip(7)
94 return
95 text.skip(3)
96
97def parse_hex_file_lines_constructor_32(text, basic_string_plt, got_plt, lines):
beb1bd5a
MC
98 text.skip(-5)
99 esi = (text.peek(1) == b'\xb3')
100 text.skip(5)
1b0c6828
MC
101 cnt = len(lines)
102 while cnt > 0:
103 if text.peek(2) == b'\x8d\x45': # lea offs8(%ebp),%eax
104 text.skip(3)
105 elif text.peek(2) == b'\x8d\x85': # lea offs32(%ebp),%eax
106 text.skip(6)
beb1bd5a 107 if text.peek(1) == (b'\xbf' if esi else b'\xbe'): # mov $imm32,%esi
1b0c6828 108 text.skip(5)
beb1bd5a 109 elif text.peek(2) == (b'\x31\xff' if esi else b'\x31\xf6'): # xor %esi,%esi
1b0c6828
MC
110 text.skip(2)
111 if text.peek(4) == b'\x89\x44\x24\x08': # mov %eax,0x8(%esp)
112 text.skip(4)
113 if text.peek(2) == b'\x8d\x83': # lea offs32(%ebx),%eax
114 straddr = struct.unpack('<i', text.peek(4, 2))[0]
115 text.skip(6)
116 straddr += got_plt
117 else:
08a979af 118 raise Exception('Expected lea offs32(%ebx),%eax @ ' +
1b0c6828
MC
119 ('0x%x' % text.address))
120 if text.peek(4) == b'\x89\x44\x24\x04': # mov %eax,0x4(%esp)
121 text.skip(4)
beb1bd5a 122 if text.peek(3) == (b'\x89\x34\x24' if esi else b'\x89\x3c\x24'): # mov %edi,(%esp)
1b0c6828
MC
123 offs = 0
124 text.skip(3)
beb1bd5a 125 elif text.peek(2) == (b'\x8d\x46' if esi else b'\x8d\x47'): # lea offs8(%edi),%eax
1b0c6828
MC
126 offs = struct.unpack('<b', text.peek(1, 2))[0]
127 text.skip(3)
beb1bd5a 128 elif text.peek(2) == (b'\x8d\x86' if esi else b'\x8d\x87'): # lea offs32(%edi),%eax
1b0c6828
MC
129 offs = struct.unpack('<i', text.peek(4, 2))[0]
130 text.skip(6)
131 else:
beb1bd5a 132 raise Exception('Expected lea offs(%e'+('s' if esi else 'd')+'i),%eax @ ' +
1b0c6828
MC
133 ('0x%x' % text.address))
134 if offs < 0 or offs > (len(lines) << 2) or (offs & 3) != 0:
135 raise Exception('Invalid offset %d' % offs)
136 index = offs >> 2
137 if lines[index] != 0:
138 raise Exception('Line %d filled multiple times' % index)
139 if text.peek(3) == b'\x89\x04\x24': # mov %eax,(%esp)
140 text.skip(3)
141 if text.peek(1) == b'\xe8': # call offs32
142 offs = struct.unpack('<i', text.peek(4, 1))[0]
143 text.skip(5)
144 if text.address + offs != basic_string_plt:
08a979af 145 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
146 ('0x%x' % text.address))
147 else:
08a979af 148 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
149 ('0x%x' % text.address))
150 if straddr == 0:
151 raise Exception('NULL pointer stored to index %d' % index)
152 lines[index] = straddr
153 cnt -= 1
154
155def parse_hex_file_lines_constructor_64(text, basic_string_plt, lines):
156 cnt = len(lines)
157 while cnt > 0:
158 if text.peek(1) == b'\xbb': # mov $imm32,%ebx
159 text.skip(5)
160 elif text.peek(2) == b'\x31\xdb': # xor %ebx,%ebx
161 text.skip(2)
162 if text.peek(4) == b'\x48\x8d\x54\x24': # lea offs8(%rsp),%rdx
163 text.skip(5)
164 elif text.peek(4) == b'\x48\x8d\x94\x24': # lea offs32(%rsp),%rdx
165 text.skip(8)
166 if text.peek(3) == b'\x48\x8d\x35': # lea offs32(%rip),%rsi
167 straddr = struct.unpack('<i', text.peek(4, 3))[0]
168 text.skip(7)
169 straddr += text.address
170 else:
08a979af 171 raise Exception('Expected lea offs(%rip),%rsi @ ' +
1b0c6828
MC
172 ('0x%x' % text.address))
173 if text.peek(3) == b'\x48\x89\xef': # mov %rbp,%rdi
174 offs = 0
175 text.skip(3)
176 elif text.peek(3) == b'\x48\x8d\x7d': # lea offs8(%rbp),%rdi
177 offs = struct.unpack('<b', text.peek(1, 3))[0]
178 text.skip(4)
179 elif text.peek(3) == b'\x48\x8d\xbd': # lea offs32(%rbp),%rdi
180 offs = struct.unpack('<i', text.peek(4, 3))[0]
181 text.skip(7)
182 else:
08a979af 183 raise Exception('Expected lea offs(%rbp),%rdi @ ' +
1b0c6828 184 ('0x%x' % text.address))
beb1bd5a
MC
185 if text.peek(1) == b'\xbb': # mov $imm32,%ebx
186 text.skip(5)
187 elif text.peek(2) == b'\x31\xdb': # xor %ebx,%ebx
188 text.skip(2)
1b0c6828
MC
189 if offs < 0 or offs > (len(lines) << 3) or (offs & 7) != 0:
190 raise Exception('Invalid offset %d' % offs)
191 index = offs >> 3
192 if lines[index] != 0:
193 raise Exception('Line %d filled multiple times' % index)
194 if text.peek(1) == b'\xe8': # callq offs32
195 offs = struct.unpack('<i', text.peek(4, 1))[0]
196 text.skip(5)
197 if text.address + offs != basic_string_plt:
08a979af 198 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
199 ('0x%x' % text.address))
200 else:
08a979af 201 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
202 ('0x%x' % text.address))
203 if straddr == 0:
204 raise Exception('NULL pointer stored to index %d' % index)
205 lines[index] = straddr
206 cnt -= 1
207
208def find_reloc(elf, symname):
209 for section, relocs in elf.relocs.items():
210 if 'symbols' in relocs and symname in relocs['symbols']:
211 symnum = relocs['symbols'][symname]['number']
212 for reloc in relocs['relocs']:
213 if reloc['r_sym'] == symnum:
214 return reloc
08a979af 215 raise Exception('Unable to find a relocation against ' + symname)
1b0c6828
MC
216
217def ihex_to_binary(lines):
218 chunks = {}
219 for line in lines:
220 if line[0] != ':':
221 raise Exception('ihex line does not start with ":"')
222 line = bytes.fromhex(line[1:])
223 if (sum(bytearray(line)) & 0xff) != 0:
224 raise Exception('Invalid checksum in ihex')
225 (byte_count, address, rectype) = struct.unpack('>BHB', line[:4])
226 (data, checksum) = struct.unpack('>%dsB' % (byte_count), line[4:])
227 if rectype == 1 and byte_count == 0:
228 pass
229 elif rectype != 0 or byte_count == 0:
230 raise Exception('Unexpected rectype %d with bytecount %d' %
231 (rectype, byte_count))
232 elif address in chunks:
233 raise Exception('Multiple ihex lines with address 0x%x' % address)
234 else:
235 chunks[address] = data
236 blob = b''
237 for address in sorted(iter(chunks)):
238 if address < len(blob):
239 raise Exception('Overlapping ihex chunks')
240 elif address > len(blob):
241 blob += b'\x00' * (address - len(blob))
242 blob += chunks[address]
243 return blob
244
245def extract_fx2_firmware(elf, symname, filename):
08a979af 246 blob = elf.load_symbol(elf.dynsym[symname + 'Count'])
1b0c6828
MC
247 count = struct.unpack('<I', blob)[0]
248 got_plt = elf.find_section('.got.plt')['sh_addr']
08a979af 249 hex_file_lines_got = find_reloc(elf, symname)['r_offset']
1b0c6828
MC
250 basic_string_got = find_reloc(elf, '_ZNSsC1EPKcRKSaIcE')['r_offset']
251 pltsec = elf.find_section('.plt')
252 plt = searcher(elf.read_section(pltsec), pltsec['sh_addr'])
253 try:
254 if elf.elf_wordsize == 64:
255 basic_string_plt = search_plt_64(plt, basic_string_got)
256 else:
257 basic_string_plt = search_plt_32(plt, basic_string_got)
258 except:
259 raise Exception('Unable to find a PLT entry for _ZNSsC1EPKcRKSaIcE')
260 textsec = elf.find_section('.text')
261 text = searcher(elf.read_section(textsec), textsec['sh_addr'])
262 while True:
263 try:
264 if elf.elf_wordsize == 64:
265 find_hex_file_lines_constructor_64(text, hex_file_lines_got)
266 else:
267 find_hex_file_lines_constructor_32(text, hex_file_lines_got,
268 got_plt)
269 except:
08a979af 270 raise Exception('Unable to find constructor for ' + symname)
1b0c6828
MC
271 oldoffs = text.offset
272 l = [0]*count
273 try:
274 if elf.elf_wordsize == 64:
275 parse_hex_file_lines_constructor_64(text, basic_string_plt, l)
276 else:
277 parse_hex_file_lines_constructor_32(text, basic_string_plt,
278 got_plt, l)
279 break
280 except KeyError:
281 text.reset(oldoffs)
282 rodatasec = elf.find_section('.rodata')
283 rodata = elf.read_section(rodatasec)
284 lo = rodatasec['sh_addr']
285 hi = lo + rodatasec['sh_size']
286 for i in range(count):
287 addr = l[i]
288 if addr < lo or addr >= hi:
289 raise Exception('Address 0x%x outside of .rodata section' % addr)
290 l[i] = elf.get_name(addr - lo, rodata)
291 blob = ihex_to_binary(l)
292 f = open(filename, 'wb')
293 f.write(blob)
294 f.close()
295 print("saved %d bytes to %s" % (len(blob), filename))
296
2c45c57a
MC
297def extract_fx2_firmware_single(elf, symname, filename):
298 if not symname in elf.dynsym:
299 return False
300 hex = bytes.decode(elf.load_symbol(elf.dynsym[symname]))
301 if hex[-1] == '\0':
302 hex = hex[:-1]
303 blob = ihex_to_binary(hex.split(';'))
304 f = open(filename, 'wb')
305 f.write(blob)
306 f.close()
307 print("saved %d bytes to %s" % (len(blob), filename))
308 return True
309
c91650ae
JL
310def extract_fx3_firmware(elf, symname, filename):
311 index = 0
312 blobs = []
313 while True:
314 sym = elf.dynsym.get(symname + '_' + str(index))
315 if not sym:
316 break
317 index += 1
318 hex = bytes.decode(elf.load_symbol(sym))
319 if hex[-1] == '\0':
320 hex = hex[:-1]
321 for part in hex.split(';'):
322 if not part:
323 continue
324 if part[0] != ':':
325 raise Exception('ihex line does not start with ":"')
326 blobs.append(bytes.fromhex(part[1:]))
327 if not blobs:
328 return
329 with open(filename, 'wb') as f:
330 for blob in blobs:
331 f.write(blob)
332 print("saved %d bytes from %d blobs to %s" % (sum(map(len, blobs)),
333 len(blobs), filename))
334
a5419fb5
MC
335def extract_symbol(elf, symname, filename):
336 blob = elf.load_symbol(elf.dynsym[symname])
337 f = open(filename, 'wb')
338 f.write(blob)
339 f.close()
340 print("saved %d bytes to %s" % (len(blob), filename))
341
c91650ae
JL
342def try_extract_symbol(elf, symname, filename):
343 if not symname in elf.dynsym:
344 return
345 extract_symbol(elf, symname, filename)
346
a5419fb5 347def extract_bitstream(elf, lv):
08a979af
UH
348 extract_symbol(elf, 'gLogic16Lv' + lv + 'CompressedBitstream',
349 'saleae-logic16-fpga-' + lv + '.bitstream')
a5419fb5
MC
350
351def usage():
231faffa 352 print("sigrok-fwextract-saleae-logic16 <programfile>")
a5419fb5
MC
353 sys.exit()
354
355
356#
357# main
358#
359
360if len(sys.argv) != 2:
361 usage()
362
363try:
364 filename = sys.argv[1]
365 elf = parseelf.elf(filename)
1b0c6828
MC
366 if elf.ehdr['e_machine'] != 3 and elf.ehdr['e_machine'] != 62:
367 raise Exception('Unsupported e_machine')
2c45c57a
MC
368 if not extract_fx2_firmware_single(elf, 'Logic16FirmwareStrings', 'saleae-logic16-fx2.fw'):
369 extract_fx2_firmware(elf, 'gLogic16HexFileLines', 'saleae-logic16-fx2.fw')
a5419fb5
MC
370 extract_bitstream(elf, '18')
371 extract_bitstream(elf, '33')
c91650ae
JL
372 extract_fx3_firmware(elf, 'LogicPro16FirmwareStrings', 'saleae-logicpro16-fx3.fw')
373 extract_fx3_firmware(elf, 'LogicPro8FirmwareStrings', 'saleae-logicpro8-fx3.fw')
374 try_extract_symbol(elf, 'gLogicPro16CompressedBitstream', 'saleae-logicpro16-fpga.bitstream')
375 try_extract_symbol(elf, 'gLogicProCompressedBitstream', 'saleae-logicpro8-fpga.bitstream')
a5419fb5
MC
376except Exception as e:
377 print("Error: %s" % str(e))