]> sigrok.org Git - sigrok-util.git/blame - firmware/saleae-logic16/sigrok-fwextract-saleae-logic16
firmware tools: Cosmetics, whitespace.
[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
50 def __init__(this, data, addr):
51 this.data = data
52 this.baseaddr = addr
53 this.length = len(data)
54 this.reset()
55
56def 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
61def 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
70def 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
79def 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
88def 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:
08a979af 106 raise Exception('Expected lea offs32(%ebx),%eax @ ' +
1b0c6828
MC
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:
08a979af 120 raise Exception('Expected lea offs(%edi),%eax @ ' +
1b0c6828
MC
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:
08a979af 133 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
134 ('0x%x' % text.address))
135 else:
08a979af 136 raise Exception('Expected call ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
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
143def 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:
08a979af 159 raise Exception('Expected lea offs(%rip),%rsi @ ' +
1b0c6828
MC
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:
08a979af 171 raise Exception('Expected lea offs(%rbp),%rdi @ ' +
1b0c6828
MC
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:
08a979af 182 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
183 ('0x%x' % text.address))
184 else:
08a979af 185 raise Exception('Expected callq ZNSsC1EPKcRKSaIcE@plt @ ' +
1b0c6828
MC
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
192def 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
08a979af 199 raise Exception('Unable to find a relocation against ' + symname)
1b0c6828
MC
200
201def 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
229def extract_fx2_firmware(elf, symname, filename):
08a979af 230 blob = elf.load_symbol(elf.dynsym[symname + 'Count'])
1b0c6828
MC
231 count = struct.unpack('<I', blob)[0]
232 got_plt = elf.find_section('.got.plt')['sh_addr']
08a979af 233 hex_file_lines_got = find_reloc(elf, symname)['r_offset']
1b0c6828
MC
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:
08a979af 254 raise Exception('Unable to find constructor for ' + symname)
1b0c6828
MC
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
a5419fb5
MC
281def 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
288def extract_bitstream(elf, lv):
08a979af
UH
289 extract_symbol(elf, 'gLogic16Lv' + lv + 'CompressedBitstream',
290 'saleae-logic16-fpga-' + lv + '.bitstream')
a5419fb5
MC
291
292def usage():
231faffa 293 print("sigrok-fwextract-saleae-logic16 <programfile>")
a5419fb5
MC
294 sys.exit()
295
296
297#
298# main
299#
300
301if len(sys.argv) != 2:
302 usage()
303
304try:
305 filename = sys.argv[1]
306 elf = parseelf.elf(filename)
1b0c6828
MC
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')
a5419fb5
MC
310 extract_bitstream(elf, '18')
311 extract_bitstream(elf, '33')
312except Exception as e:
313 print("Error: %s" % str(e))