gdbflasher 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,208 @@
1
+ module GdbFlasher
2
+
3
+ class MCU
4
+ def initialize(connection)
5
+ @connection = connection
6
+ @symbol_table = {}
7
+
8
+ helper_hex = File.join HELPERS, helper_name + ".hex"
9
+ helper_sym = File.join HELPERS, helper_name + ".sym"
10
+
11
+ File.open(helper_sym, "r") do |f|
12
+ f.each_line do |line|
13
+ line.rstrip!
14
+
15
+ address, symbol = line.split(" ")
16
+
17
+ @symbol_table[symbol.intern] = address.to_i 16
18
+ end
19
+ end
20
+
21
+ helper = nil
22
+ File.open(helper_hex, "r") { |f| helper = IHex.load f }
23
+
24
+ connection.reset
25
+ connection.get_stop_reply
26
+
27
+ helper.segments.each do |segment|
28
+ connection.write_memory segment.base, segment.data
29
+ end
30
+
31
+ initialize_environment
32
+
33
+ @connection.insert_breakpoint 0, @symbol_table[:trap], breakpoint_kind
34
+
35
+ call_helper :initialize
36
+ end
37
+
38
+ def finalize
39
+ call_helper :finalize
40
+
41
+ @connection.remove_breakpoint 0, @symbol_table[:trap], breakpoint_kind
42
+ end
43
+
44
+ def program_ihex(ihex, extra = {})
45
+ sectors = []
46
+ sector_actions = []
47
+
48
+ sector_map = self.class.const_get :SECTORS
49
+
50
+ sector_map.each_index do |i|
51
+ sector_begin, sector_end = sector_map[i]
52
+
53
+ sector_segment = nil
54
+
55
+ ihex.segments.each do |segment|
56
+ intersection = segment.intersect sector_begin, sector_end
57
+
58
+ if intersection.size > 0
59
+ if sector_segment.nil?
60
+ sector_segment = IHex::Segment.new
61
+ sector_segment.base = sector_begin
62
+ sector_segment.data = blank_byte.chr * (sector_end - sector_begin + 1)
63
+
64
+ sectors << sector_segment
65
+ end
66
+
67
+ affected_range = intersection.base - sector_begin...intersection.base + intersection.size - sector_begin
68
+ sector_segment.data[affected_range] = intersection.data
69
+ end
70
+ end
71
+ end
72
+
73
+ if extra[:read_disallowed]
74
+ sector_actions = [ [ :erase, :program ] ] * sectors.count
75
+ else
76
+ puts "Checking sectors"
77
+
78
+ sectors.each do |sector|
79
+ data = @connection.read_memory sector.base, sector.size
80
+
81
+ if data == sector.data
82
+ sector_actions << [ ]
83
+ elsif is_blank(data)
84
+ sector_actions << [ :program, :verify ]
85
+ elsif is_blank(sector.data)
86
+ sector_actions << [ :erase, :verify ]
87
+ else
88
+ sector_actions << [ :erase, :program, :verify ]
89
+ end
90
+ end
91
+ end
92
+
93
+ puts "Programming sectors:"
94
+ sectors.each_index do |i|
95
+ sector = sectors[i]
96
+ actions = sector_actions[i]
97
+
98
+ next if actions.empty?
99
+
100
+ sector_id = sector_map.index { |i| i[0] == sector.base }
101
+
102
+ printf " - %08X - %08X: ", sector.base, sector.base + sector.size - 1
103
+
104
+ actions.each do |action|
105
+ case action
106
+ when :erase
107
+ print "erase, "
108
+
109
+ status = erase sector_id
110
+ if status != 0
111
+ puts "failure!"
112
+ warn "Unable to erase sector #{sector_id}, vendor-specific error #{status}"
113
+
114
+ return false
115
+ end
116
+
117
+ when :program
118
+ print "program, "
119
+
120
+ offset = 0
121
+ page_size = @symbol_table[:PAGE_SIZE]
122
+ buffer = @symbol_table[:page_buffer]
123
+
124
+ while offset < sector.size
125
+ @connection.write_memory buffer, sector.data[offset...offset + page_size]
126
+ status = program sector.base + offset
127
+
128
+ if status != 0
129
+ puts "failure!"
130
+ warn "Unable to program page #{(sector.base + offset).to_s 16}, vendor-specific error #{status}"
131
+
132
+ return false
133
+ end
134
+
135
+ offset += page_size
136
+ end
137
+
138
+ when :verify
139
+ print "verify, "
140
+
141
+ data = @connection.read_memory sector.base, sector.size
142
+
143
+ if data != sector.data
144
+ puts "failure!"
145
+ warn "Verification of sector #{sector_id} failed."
146
+
147
+ return false
148
+ end
149
+ end
150
+ end
151
+
152
+ print "all ok.\n"
153
+ end
154
+
155
+ true
156
+ end
157
+
158
+ protected
159
+
160
+ def blank_byte
161
+ 0xFF
162
+ end
163
+
164
+ def erase(sector_id)
165
+ call_helper :erase, sector_id
166
+ end
167
+
168
+ def program(offset)
169
+ call_helper :program, offset
170
+ end
171
+
172
+ def is_blank(string)
173
+ string.bytes.all? { |byte| byte == blank_byte }
174
+ end
175
+
176
+ def initialize_environment
177
+
178
+ end
179
+
180
+ def breakpoint_kind
181
+ 4 # ARM breakpoint
182
+ end
183
+
184
+ def lr_offset
185
+ 0
186
+ end
187
+
188
+ def call_helper(func, r0 = 0, r1 = 0, r2 = 0, r3 = 0)
189
+ regs = @connection.read_registers
190
+ regs[0..3] = r0, r1, r2, r3
191
+ regs[14] = @symbol_table[:trap] | lr_offset
192
+ regs[15] = @symbol_table[func]
193
+ @connection.write_registers regs
194
+
195
+ reply = @connection.continue
196
+
197
+ regs = @connection.read_registers
198
+
199
+ if reply.signal != 0x05 || regs[15] != @symbol_table[:trap]
200
+ raise "Target stopped by signal #{reply.signal}, PC = #{regs[15].to_s 16}"
201
+ end
202
+
203
+ regs[0]
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,334 @@
1
+ require "socket"
2
+
3
+ module GdbFlasher
4
+ class ServerConnection
5
+ class StopReply
6
+ attr_accessor :signal, :attributes
7
+
8
+ def initialize(signal, attributes = {})
9
+ @signal = signal
10
+ @attributes = attributes
11
+ end
12
+ end
13
+
14
+ def initialize
15
+ @socket = nil
16
+ end
17
+
18
+ def connect(address)
19
+ raise "Already connected" unless @socket.nil?
20
+
21
+ delimiter = address.rindex ':'
22
+ raise ArgumentError, "Port must be specified" if delimiter.nil?
23
+ host = address[0...delimiter]
24
+ port = address[delimiter + 1..-1].to_i
25
+
26
+ @buf = ""
27
+ @features = {}
28
+ @need_ack = true
29
+
30
+ begin
31
+ @socket = TCPSocket.new host, port
32
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
33
+
34
+ # Query stub features
35
+ command("qSupported").split(';').each do |feature|
36
+ if feature[-1] == '-'
37
+ @features.delete feature[0...-1].intern
38
+ elsif feature[-1] == '+'
39
+ @features[feature[0...-1].intern] = true
40
+ else
41
+ sep = feature.index '='
42
+
43
+ if sep.nil?
44
+ raise "Unexpected item in qSupported response: #{sep}"
45
+ end
46
+
47
+ @features[feature[0...sep].intern] = feature[sep + 1..-1]
48
+ end
49
+ end
50
+
51
+ if @features[:PacketSize].nil?
52
+ raise "PacketSize isn't present in qSupported response"
53
+ end
54
+
55
+ if @features[:QStartNoAckMode]
56
+ response = command("QStartNoAckMode")
57
+
58
+ if response != "OK"
59
+ raise "Unable to enter NoAck mode: #{response}"
60
+ end
61
+
62
+ @need_ack = false
63
+ end
64
+
65
+ # Load target description
66
+ if @features[:"qXfer:features:read"]
67
+ description = read_xfer "features", "target.xml"
68
+
69
+ # TODO: parse target description and use register map from it.
70
+ end
71
+
72
+ rescue Exception => e
73
+ @socket.close unless @socket.nil?
74
+ @socket = nil
75
+
76
+ raise e
77
+ end
78
+ end
79
+
80
+ def close
81
+ @socket.close
82
+ @socket = nil
83
+ end
84
+
85
+ def read_registers
86
+ response = command "g"
87
+
88
+ if (response[0] != 'E' || response.length != 3) && response.length % 8 != 0
89
+ raise "Malformed 'g' response"
90
+ end
91
+
92
+ if response[0] == 'E'
93
+ raise "GDB server error: #{response[1..2].to_i 16}"
94
+ end
95
+
96
+ Array.new response.length / 8 do |i|
97
+ big2native response[i * 8...(i + 1) * 8].to_i(16)
98
+ end
99
+ end
100
+
101
+ def write_registers(regs)
102
+ send_packet "G" + regs.map { |r| sprintf "%08x", native2big(r) }.join
103
+ end
104
+
105
+ def write_memory(base, string)
106
+ max_size = (@features[:PacketSize].to_i - 19) & ~1
107
+
108
+ offset = 0
109
+ data = string.unpack("C*").map { |byte| sprintf "%02X", byte }.join
110
+
111
+ while offset < data.length
112
+ chunk_size = [ max_size, data.length - offset ].min
113
+
114
+ response = command "M#{base.to_s 16},#{(chunk_size / 2).to_s 16}:#{data[offset...offset + chunk_size]}"
115
+
116
+ if response != "OK"
117
+ raise "Memory write failed: #{response}"
118
+ end
119
+
120
+ base += chunk_size / 2
121
+ offset += chunk_size
122
+ end
123
+ end
124
+
125
+ def read_memory(base, size)
126
+ max_size = (@features[:PacketSize].to_i - 19) & ~1
127
+ offset = 0
128
+ data = ""
129
+ size *= 2
130
+
131
+ while offset < size
132
+ chunk_size = [ max_size, size - offset ].min
133
+
134
+ response = command "m#{base.to_s 16},#{(chunk_size / 2).to_s 16}"
135
+
136
+ if response[0] == "E"
137
+ raise "Memory read failed: #{response}"
138
+ end
139
+
140
+ data += response
141
+
142
+ base += chunk_size / 2
143
+ offset += chunk_size
144
+ end
145
+
146
+ Array.new(data.length / 2) do |i|
147
+ data[i * 2..i * 2 + 1].to_i 16
148
+ end.pack("C*")
149
+ end
150
+
151
+ def insert_breakpoint(type, address, kind)
152
+ response = command "Z#{type.to_s 16},#{address.to_s 16},#{kind.to_s 16}"
153
+
154
+ if response != "OK"
155
+ raise "Breakpoint insertion failed: #{response}"
156
+ end
157
+ end
158
+
159
+ def remove_breakpoint(type, address, kind)
160
+ response = command "z#{type.to_s 16},#{address.to_s 16},#{kind.to_s 16}"
161
+
162
+ if response != "OK"
163
+ raise "Breakpoint removal failed: #{response}"
164
+ end
165
+ end
166
+
167
+ def continue
168
+ parse_stop_reply command("c")
169
+ end
170
+
171
+ def step
172
+ parse_stop_reply command("s")
173
+ end
174
+
175
+ def reset
176
+ reply = command "r"
177
+
178
+ if reply != "OK"
179
+ raise "Reset failed: #{reply}"
180
+ end
181
+ end
182
+
183
+ def get_stop_reply
184
+ parse_stop_reply command("?")
185
+ end
186
+
187
+ def write_register(reg, value)
188
+ reply = command sprintf("P%x=%08x", reg, big2native(value))
189
+
190
+ if reply != "OK"
191
+ raise "Register write failed: #{reply}"
192
+ end
193
+ end
194
+
195
+ protected
196
+
197
+ def parse_stop_reply(reply)
198
+ case reply[0]
199
+ when 'S'
200
+ StopReply.new reply[1..2].to_i(16)
201
+
202
+ when 'T'
203
+ pairs = reply[3..-1].split ';'
204
+ values = {}
205
+ pairs.each do |pair|
206
+ key, value = pair.split ':'
207
+
208
+ if key.match /^[0-9a-fA-F]+/
209
+ values[key.to_i 16] = value.to_i 16
210
+ else
211
+ values[key] = value
212
+ end
213
+ end
214
+
215
+ StopReply.new reply[1..2].to_i(16), values
216
+
217
+ else
218
+ raise "Unknown stop reply: #{reply}"
219
+ end
220
+ end
221
+
222
+ def read_xfer(object, annex)
223
+ offset = 0
224
+ size = @features[:PacketSize].to_i - 4
225
+ contents = ""
226
+
227
+ loop do
228
+ response = command "qXfer:#{object}:read:#{annex}:#{offset}:#{size}"
229
+
230
+ offset += response.length - 1
231
+ contents += response[1..-1]
232
+
233
+ if response[0] == 'l'
234
+ break
235
+ elsif response[0] != 'm'
236
+ raise "qXfer failed: #{response}"
237
+
238
+ end
239
+ end
240
+
241
+ contents
242
+ end
243
+
244
+ def command(s, extra = {})
245
+ send_packet s, extra
246
+
247
+ response = receive_packet
248
+ end
249
+
250
+ def send_packet(data, extra = {})
251
+ data = data.dup
252
+
253
+ if extra[:escape]
254
+ i = 0
255
+
256
+ while i < data.length
257
+ byte = data[i]
258
+
259
+ if byte == '$' || byte == '#' || byte == 0x7d.chr
260
+ data.insert i, 0x7d.chr
261
+
262
+ i += 1
263
+ end
264
+
265
+ i += 1
266
+ end
267
+ end
268
+
269
+ message = sprintf "$%s#%02x", data, data.sum(8)
270
+
271
+ if !@features[:PacketSize].nil? && @features[:PacketSize].to_i < message.length
272
+ raise "Internal error: message is too long"
273
+ end
274
+
275
+ @socket.write message
276
+
277
+ if @need_ack
278
+ loop do
279
+ ack = @socket.read 1
280
+
281
+ case ack
282
+ when '+'
283
+ break
284
+
285
+ when '-'
286
+ @socket.write message
287
+
288
+ else
289
+ raise "Unexpected response from server: #{ack}"
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ def receive_packet
296
+ loop do
297
+ @buf += @socket.readpartial 4096
298
+
299
+ if @buf[0] != '$'
300
+ raise "Invalid response from server"
301
+ end
302
+
303
+ idx = @buf.index '#'
304
+
305
+ if !idx.nil? && idx + 2 <= @buf.length
306
+ message = @buf.slice! 0..idx + 2
307
+
308
+ data = message[1..-4]
309
+
310
+ if @need_ack
311
+ checksum = data.sum 8
312
+ if checksum != message[-2..-1].to_i(16)
313
+ @socket.write '-'
314
+ else
315
+ @socket.write '+'
316
+
317
+ return data
318
+ end
319
+ else
320
+ return data
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ def native2big(v)
327
+ [ v ].pack("N").unpack("L")[0]
328
+ end
329
+
330
+ def big2native(v)
331
+ [ v ].pack("L").unpack("N")[0]
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,3 @@
1
+ module GdbFlasher
2
+ VERSION = "1.0.0"
3
+ end
data/lib/gdbflasher.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "gdbflasher/version"
2
+ require "gdbflasher/ihex"
3
+ require "gdbflasher/server_connection"
4
+ require "gdbflasher/mcu"
5
+ require "gdbflasher/cortexm"
6
+ require "gdbflasher/mcu/stm32f4xx"
7
+ require "gdbflasher/mcu/stm32l1xx"
8
+ require "gdbflasher/mcu/stm32f10xx_hd"
9
+ require "gdbflasher/mcu/stm32f10xx_md"
10
+
11
+
12
+ module GdbFlasher
13
+ MCU_CLASSES = {
14
+ stm32f4xx: Stm32f4xx,
15
+ stm32l1xx: Stm32l1xx,
16
+ stm32f10xx_hd: Stm32f10xxHD,
17
+ stm32f10xx_md: Stm32f10xxMD
18
+ }
19
+
20
+ HELPERS = File.join File.dirname(__FILE__), "..", "helpers"
21
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gdbflasher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergey Gridasov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trollop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: gdbflasher is a gdbserver-compatible tool for loading firmware into ARM
28
+ MCUs.
29
+ email:
30
+ - grindars@gmail.com
31
+ executables:
32
+ - gdbflasher
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - .gitignore
37
+ - Gemfile
38
+ - LICENSE
39
+ - README.md
40
+ - Rakefile
41
+ - bin/gdbflasher
42
+ - gdbflasher.gemspec
43
+ - helper_sources/Makefile
44
+ - helper_sources/stm32f10xx_hd.S
45
+ - helper_sources/stm32f10xx_hd.lds
46
+ - helper_sources/stm32f10xx_md.S
47
+ - helper_sources/stm32f10xx_md.lds
48
+ - helper_sources/stm32f4xx.S
49
+ - helper_sources/stm32f4xx.lds
50
+ - helper_sources/stm32l1xx.S
51
+ - helper_sources/stm32l1xx.lds
52
+ - helpers/stm32f10xx_hd.hex
53
+ - helpers/stm32f10xx_hd.sym
54
+ - helpers/stm32f10xx_md.hex
55
+ - helpers/stm32f10xx_md.sym
56
+ - helpers/stm32f4xx.hex
57
+ - helpers/stm32f4xx.sym
58
+ - helpers/stm32l1xx.hex
59
+ - helpers/stm32l1xx.sym
60
+ - lib/gdbflasher.rb
61
+ - lib/gdbflasher/cortexm.rb
62
+ - lib/gdbflasher/ihex.rb
63
+ - lib/gdbflasher/mcu.rb
64
+ - lib/gdbflasher/mcu/stm32f10xx_hd.rb
65
+ - lib/gdbflasher/mcu/stm32f10xx_md.rb
66
+ - lib/gdbflasher/mcu/stm32f4xx.rb
67
+ - lib/gdbflasher/mcu/stm32l1xx.rb
68
+ - lib/gdbflasher/server_connection.rb
69
+ - lib/gdbflasher/version.rb
70
+ homepage: https://github.com/grindars/gdbflasher
71
+ licenses: []
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.0.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Retargetable flasher for ARM microcontrollers
93
+ test_files: []