]>
Commit | Line | Data |
---|---|---|
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 | ||
21 | import sys | |
1b0c6828 | 22 | import struct |
a5419fb5 MC |
23 | import parseelf |
24 | ||
1b0c6828 MC |
25 | class 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 | ||
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: | |
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 | ||
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: | |
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 | ||
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 | |
08a979af | 199 | raise Exception('Unable to find a relocation against ' + symname) |
1b0c6828 MC |
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): | |
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 |
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): | |
08a979af UH |
289 | extract_symbol(elf, 'gLogic16Lv' + lv + 'CompressedBitstream', |
290 | 'saleae-logic16-fpga-' + lv + '.bitstream') | |
a5419fb5 MC |
291 | |
292 | def usage(): | |
231faffa | 293 | print("sigrok-fwextract-saleae-logic16 <programfile>") |
a5419fb5 MC |
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) | |
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') | |
312 | except Exception as e: | |
313 | print("Error: %s" % str(e)) |