3 ## This file is part of the sigrok-util project.
5 ## Copyright (C) 2013 Marcus Comstedt <marcus@mc.pp.se>
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.
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.
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/>.
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
34 if this.offset + cnt > this.length:
35 raise Exception('Skip past end of section')
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]
44 def look_for(this, needle):
45 pos = this.data.find(needle, this.offset)
47 raise Exception('Needle not found in haystack')
48 this.skip(pos - this.offset)
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:
57 this.skip(pos1 - this.offset)
59 def __init__(this, data, addr):
62 this.length = len(data)
65 def search_plt_32(plt, addr):
67 plt.look_for(struct.pack('<BBI', 0xff, 0x25, addr)) # jmp *addr32
70 def search_plt_64(plt, addr):
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:
79 def find_hex_file_lines_constructor_32(text, hex_file_lines_got, got_plt):
81 text.look_for_either(b'\x8b\xbb', b'\x8b\xb3') # mov offs32(%ebx),{%edi,%esi}
82 offs = struct.unpack('<i', text.peek(4, 2))[0]
83 if got_plt + offs == hex_file_lines_got:
88 def find_hex_file_lines_constructor_64(text, hex_file_lines_got):
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:
97 def parse_hex_file_lines_constructor_32(text, basic_string_plt, got_plt, lines):
99 esi = (text.peek(1) == b'\xb3')
103 if text.peek(2) == b'\x8d\x45': # lea offs8(%ebp),%eax
105 elif text.peek(2) == b'\x8d\x85': # lea offs32(%ebp),%eax
107 if text.peek(1) == (b'\xbf' if esi else b'\xbe'): # mov $imm32,%esi
109 elif text.peek(2) == (b'\x31\xff' if esi else b'\x31\xf6'): # xor %esi,%esi
111 if text.peek(4) == b'\x89\x44\x24\x08': # mov %eax,0x8(%esp)
113 if text.peek(2) == b'\x8d\x83': # lea offs32(%ebx),%eax
114 straddr = struct.unpack('<i', text.peek(4, 2))[0]
118 raise Exception('Expected lea offs32(%ebx),%eax @ ' +
119 ('0x%x' % text.address))
120 if text.peek(4) == b'\x89\x44\x24\x04': # mov %eax,0x4(%esp)
122 if text.peek(3) == (b'\x89\x34\x24' if esi else b'\x89\x3c\x24'): # mov %edi,(%esp)
125 elif text.peek(2) == (b'\x8d\x46' if esi else b'\x8d\x47'): # lea offs8(%edi),%eax
126 offs = struct.unpack('<b', text.peek(1, 2))[0]
128 elif text.peek(2) == (b'\x8d\x86' if esi else b'\x8d\x87'): # lea offs32(%edi),%eax
129 offs = struct.unpack('<i', text.peek(4, 2))[0]
132 raise Exception('Expected lea offs(%e'+('s' if esi else 'd')+'i),%eax @ ' +
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)
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)
141 if text.peek(1) == b'\xe8': # call offs32
142 offs = struct.unpack('<i', text.peek(4, 1))[0]
144 if text.address + offs != basic_string_plt:
145 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
146 ('0x%x' % text.address))
148 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
149 ('0x%x' % text.address))
151 raise Exception('NULL pointer stored to index %d' % index)
152 lines[index] = straddr
155 def parse_hex_file_lines_constructor_64(text, basic_string_plt, lines):
158 if text.peek(1) == b'\xbb': # mov $imm32,%ebx
160 elif text.peek(2) == b'\x31\xdb': # xor %ebx,%ebx
162 if text.peek(4) == b'\x48\x8d\x54\x24': # lea offs8(%rsp),%rdx
164 elif text.peek(4) == b'\x48\x8d\x94\x24': # lea offs32(%rsp),%rdx
166 if text.peek(3) == b'\x48\x8d\x35': # lea offs32(%rip),%rsi
167 straddr = struct.unpack('<i', text.peek(4, 3))[0]
169 straddr += text.address
171 raise Exception('Expected lea offs(%rip),%rsi @ ' +
172 ('0x%x' % text.address))
173 if text.peek(3) == b'\x48\x89\xef': # mov %rbp,%rdi
176 elif text.peek(3) == b'\x48\x8d\x7d': # lea offs8(%rbp),%rdi
177 offs = struct.unpack('<b', text.peek(1, 3))[0]
179 elif text.peek(3) == b'\x48\x8d\xbd': # lea offs32(%rbp),%rdi
180 offs = struct.unpack('<i', text.peek(4, 3))[0]
183 raise Exception('Expected lea offs(%rbp),%rdi @ ' +
184 ('0x%x' % text.address))
185 if text.peek(1) == b'\xbb': # mov $imm32,%ebx
187 elif text.peek(2) == b'\x31\xdb': # xor %ebx,%ebx
189 if offs < 0 or offs > (len(lines) << 3) or (offs & 7) != 0:
190 raise Exception('Invalid offset %d' % offs)
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]
197 if text.address + offs != basic_string_plt:
198 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
199 ('0x%x' % text.address))
201 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
202 ('0x%x' % text.address))
204 raise Exception('NULL pointer stored to index %d' % index)
205 lines[index] = straddr
208 def 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:
215 raise Exception('Unable to find a relocation against ' + symname)
217 def ihex_to_binary(lines):
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:
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)
235 chunks[address] = data
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]
245 def extract_fx2_firmware(elf, symname, filename):
246 blob = elf.load_symbol(elf.dynsym[symname + 'Count'])
247 count = struct.unpack('<I', blob)[0]
248 got_plt = elf.find_section('.got.plt')['sh_addr']
249 hex_file_lines_got = find_reloc(elf, symname)['r_offset']
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'])
254 if elf.elf_wordsize == 64:
255 basic_string_plt = search_plt_64(plt, basic_string_got)
257 basic_string_plt = search_plt_32(plt, basic_string_got)
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'])
264 if elf.elf_wordsize == 64:
265 find_hex_file_lines_constructor_64(text, hex_file_lines_got)
267 find_hex_file_lines_constructor_32(text, hex_file_lines_got,
270 raise Exception('Unable to find constructor for ' + symname)
271 oldoffs = text.offset
274 if elf.elf_wordsize == 64:
275 parse_hex_file_lines_constructor_64(text, basic_string_plt, l)
277 parse_hex_file_lines_constructor_32(text, basic_string_plt,
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):
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')
295 print("saved %d bytes to %s" % (len(blob), filename))
297 def extract_fx2_firmware_single(elf, symname, filename):
298 if not symname in elf.dynsym:
300 hex = bytes.decode(elf.load_symbol(elf.dynsym[symname]))
303 blob = ihex_to_binary(hex.split(';'))
304 f = open(filename, 'wb')
307 print("saved %d bytes to %s" % (len(blob), filename))
310 def extract_fx3_firmware(elf, symname, filename):
314 sym = elf.dynsym.get(symname + '_' + str(index))
318 hex = bytes.decode(elf.load_symbol(sym))
321 for part in hex.split(';'):
325 raise Exception('ihex line does not start with ":"')
326 blobs.append(bytes.fromhex(part[1:]))
329 with open(filename, 'wb') as f:
332 print("saved %d bytes from %d blobs to %s" % (sum(map(len, blobs)),
333 len(blobs), filename))
335 def extract_symbol(elf, symname, filename):
336 blob = elf.load_symbol(elf.dynsym[symname])
337 f = open(filename, 'wb')
340 print("saved %d bytes to %s" % (len(blob), filename))
342 def try_extract_symbol(elf, symname, filename):
343 if not symname in elf.dynsym:
345 extract_symbol(elf, symname, filename)
347 def extract_bitstream(elf, lv):
348 extract_symbol(elf, 'gLogic16Lv' + lv + 'CompressedBitstream',
349 'saleae-logic16-fpga-' + lv + '.bitstream')
352 print("sigrok-fwextract-saleae-logic16 <programfile>")
360 if len(sys.argv) != 2:
364 filename = sys.argv[1]
365 elf = parseelf.elf(filename)
366 if elf.ehdr['e_machine'] != 3 and elf.ehdr['e_machine'] != 62:
367 raise Exception('Unsupported e_machine')
368 if not extract_fx2_firmware_single(elf, 'Logic16FirmwareStrings', 'saleae-logic16-fx2.fw'):
369 extract_fx2_firmware(elf, 'gLogic16HexFileLines', 'saleae-logic16-fx2.fw')
370 extract_bitstream(elf, '18')
371 extract_bitstream(elf, '33')
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')
376 except Exception as e:
377 print("Error: %s" % str(e))