gdbflasher 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []