kaitai-struct-visualizer 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Kaitai Struct: visualizer
2
+
3
+ This is a simple visualizer for [Kaitai Struct](https://github.com/kaitai-io/kaitai_struct) project.
4
+
5
+ Kaitai Struct is a declarative language used for describe various
6
+ binary data structures, laid out in files or in memory: i.e. binary
7
+ file formats, network stream packet formats, etc.
8
+
9
+ The main idea is that a particular format is described in Kaitai
10
+ Struct language (`.ksy` files) only once and then can be compiled with
11
+ this compiler into source files in one of the supported programming
12
+ languages. These modules will include a generated code for a parser
13
+ that can read described data structure from a file / stream and give
14
+ access to it in a nice, easy-to-comprehend API.
15
+
16
+ Please refer to [documentation in Kaitai Struct project](https://github.com/kaitai-io/kaitai_struct)
17
+ for details on `.ksy` files and general usage patterns.
18
+
19
+ ## Downloading and installing
20
+
21
+ ### Generic
22
+
23
+ KS visualizer is written in Ruby and is available as .gem package.
24
+
25
+ ### Source code
26
+
27
+ If you're interested in developing the visualizer itself, you can check
28
+ out source code in repository:
29
+
30
+ git clone https://github.com/kaitai-io/kaitai_struct_visualizer
31
+
32
+ ## Usage
33
+
34
+ `ksv <binary-file> <ksy-file>...`
35
+
36
+ ## Licensing
37
+
38
+ Kaitai Struct visualizer itself is copyright (C) 2015-2016 Kaitai
39
+ Project.
40
+
41
+ This program is free software: you can redistribute it and/or modify
42
+ it under the terms of the GNU General Public License as published by
43
+ the Free Software Foundation, either version 3 of the License, or (at
44
+ your option) any later version.
45
+
46
+ This program is distributed in the hope that it will be useful, but
47
+ WITHOUT ANY WARRANTY; without even the implied warranty of
48
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
49
+ General Public License for more details.
50
+
51
+ You should have received a copy of the GNU General Public License
52
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
53
+
54
+ Note that it applies only to compiler itself, not `.ksy` input files
55
+ that one supplies in normal process of compilation, nor to compiler's
56
+ output files — that consitutes normal usage process and you obviously
57
+ keep copyright to both.
data/bin/ksv ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Some additional magic to make it work right after repo checkout,
4
+ # without installation to proper Ruby library dirs
5
+
6
+ full_bin_path = File.realpath($PROGRAM_NAME)
7
+ dist_path = File.expand_path(File.dirname(full_bin_path) + '/..')
8
+
9
+ $LOAD_PATH << "#{dist_path}/lib"
10
+ $LOAD_PATH << "#{dist_path}/../runtime/ruby/lib"
11
+
12
+ require 'kaitai/struct/visualizer'
13
+
14
+ if ARGV.size < 2
15
+ puts "Usage: #{File.basename($PROGRAM_NAME)} <file_to_parse.bin> <format.yaml>..."
16
+ exit 1
17
+ end
18
+
19
+ v = Kaitai::Struct::Visualizer::ExternalCompilerVisualizer.new(ARGV[0], ARGV[1..-1])
20
+ v.run
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path("../lib/kaitai/struct/visualizer/version", __FILE__)
4
+ require 'date'
5
+
6
+ Gem::Specification.new { |s|
7
+ s.name = 'kaitai-struct-visualizer'
8
+ s.version = Kaitai::Struct::Visualizer::VERSION
9
+ s.date = Date.today.to_s
10
+
11
+ s.authors = ['Mikhail Yakshin']
12
+ s.email = 'greycat@kaitai.io'
13
+
14
+ s.homepage = 'http://kaitai.io'
15
+ s.summary = 'Advanced hex viewer and binary structure exploration tool (visualizer) using Kaitai Struct ksy files'
16
+ s.license = 'GPL-3.0+'
17
+ s.description = <<-EOF
18
+ Kaitai Struct is a declarative language used for describe various binary data structures, laid out in files or in memory: i.e. binary file formats, network stream packet formats, etc.
19
+
20
+ The main idea is that a particular format is described in Kaitai Struct language (.ksy file) and then can be compiled with ksc into source files in one of the supported programming languages. These modules will include a generated code for a parser that can read described data structure from a file / stream and give access to it in a nice, easy-to-comprehend API.
21
+
22
+ This package is a visualizer tool for .ksy files. Given a particular binary file and .ksy file(s) that describe its format, it can visualize internal data structures in a tree form and a multi-level highlight hex viewer.
23
+ EOF
24
+
25
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
26
+ s.require_paths = ['lib']
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
31
+
32
+ s.add_development_dependency "bundler", "~> 1.3"
33
+ s.add_development_dependency 'rake', '~> 10'
34
+ # s.add_development_dependency 'rspec', '~> 3'
35
+
36
+ s.add_dependency 'kaitai-struct', "~> #{s.version}"
37
+ }
@@ -0,0 +1,2 @@
1
+ require 'kaitai/struct/visualizer/version'
2
+ require 'kaitai/struct/visualizer/visualizer_main'
@@ -0,0 +1,294 @@
1
+ require 'kaitai/struct/visualizer/version'
2
+
3
+ module Kaitai::Struct::Visualizer
4
+ class HexViewer
5
+ def initialize(ui, buf, shift_x = 0, tree = nil)
6
+ @ui = ui
7
+ @buf = buf
8
+ @shift_x = shift_x
9
+ @tree = tree
10
+
11
+ @embedded = not(tree.nil?)
12
+ @max_scr_ln = @ui.rows - 3
13
+
14
+ @addr = 0
15
+ @scroll_y = 0
16
+ reset_cur
17
+ raise if @cur_x.nil?
18
+ end
19
+
20
+ def buf=(buf)
21
+ @buf = buf
22
+ end
23
+
24
+ def addr; @addr; end
25
+ def addr=(a)
26
+ @addr = a
27
+ reset_cur
28
+ end
29
+
30
+ def reset_cur
31
+ @cur_y = addr_to_row(@addr)
32
+ @cur_x = addr_to_col(@addr)
33
+ end
34
+
35
+ def highlight(regions)
36
+ highlight_hide
37
+ @hl_regions = regions
38
+ highlight_show
39
+ end
40
+
41
+ def ensure_visible
42
+ scr_y = row_to_scr(@cur_y)
43
+ if scr_y < 0
44
+ @scroll_y = @cur_y
45
+ redraw
46
+ highlight_show
47
+ elsif scr_y > @max_scr_ln
48
+ @scroll_y = @cur_y - @max_scr_ln
49
+ redraw
50
+ highlight_show
51
+ end
52
+ end
53
+
54
+ def run
55
+ c = nil
56
+ loop {
57
+ @ui.goto(0, @max_scr_ln + 1)
58
+ printf "%06x (%d, %d)", @addr, @cur_x, @cur_y
59
+
60
+ @ui.goto(col_to_col_char(@cur_x), row_to_scr(@cur_y))
61
+ c = @ui.read_char_mapped
62
+ case c
63
+ when :tab
64
+ return if @embedded
65
+ when :left_arrow
66
+ if @addr > 0
67
+ @addr -= 1
68
+ @cur_x -= 1
69
+ if @cur_x < 0
70
+ @cur_y -= 1
71
+ @cur_x = PER_LINE - 1
72
+ end
73
+ end
74
+ when :right_arrow
75
+ if @addr < @buf.size
76
+ @addr += 1
77
+ @cur_x += 1
78
+ if @cur_x >= PER_LINE
79
+ @cur_y += 1
80
+ @cur_x = 0
81
+ end
82
+ end
83
+ when :up_arrow
84
+ @addr -= PER_LINE
85
+ @cur_y -= 1
86
+ clamp_cursor
87
+ when :down_arrow
88
+ @addr += PER_LINE
89
+ @cur_y += 1
90
+ clamp_cursor
91
+ when :pg_dn
92
+ @addr += PER_LINE * PAGE_ROWS
93
+ @cur_y += PAGE_ROWS
94
+ clamp_cursor
95
+ when :pg_up
96
+ @addr -= PER_LINE * PAGE_ROWS
97
+ @cur_y -= PAGE_ROWS
98
+ clamp_cursor
99
+ when :home
100
+ if @cur_x == 0
101
+ @addr = 0
102
+ @cur_y = 0
103
+ else
104
+ @addr -= @cur_x
105
+ @cur_x = 0
106
+ end
107
+ clamp_cursor
108
+ when :end
109
+ if @cur_x == PER_LINE - 1
110
+ @addr = @buf.size - 1
111
+ reset_cur
112
+ else
113
+ @addr = @addr - @cur_x + PER_LINE - 1
114
+ @cur_x = PER_LINE - 1
115
+ end
116
+ clamp_cursor
117
+ when 'q'
118
+ @tree.do_exit if @tree
119
+ return
120
+ end
121
+
122
+ ensure_visible
123
+ }
124
+ end
125
+
126
+ def clamp_cursor
127
+ if @addr < 0
128
+ @addr = 0
129
+ @cur_x = 0
130
+ @cur_y = 0
131
+ elsif @addr >= @buf.size
132
+ @addr = @buf.size - 1
133
+ reset_cur
134
+ end
135
+ end
136
+
137
+ PER_LINE = 16
138
+ PER_GROUP = 4
139
+ PAGE_ROWS = 20
140
+ FMT = "%06x: %-#{PER_LINE * 3}s| %-#{PER_LINE}s\n"
141
+
142
+ def self.line_width
143
+ #6 + 2 + 3 * PER_LINE + 2 + PER_LINE
144
+ 10 + 4 * PER_LINE
145
+ end
146
+
147
+ def col_to_col_hex(c)
148
+ #6 + 2 + 3 * c
149
+ @shift_x + 8 + 3 * c
150
+ end
151
+
152
+ def col_to_col_char(c)
153
+ #6 + 2 + 3 * PER_LINE + 2
154
+ @shift_x + 10 + 3 * PER_LINE + c
155
+ end
156
+
157
+ def row_to_scr(r)
158
+ r - @scroll_y
159
+ end
160
+
161
+ def redraw
162
+ i = row_col_to_addr(@scroll_y, 0)
163
+ row = 0
164
+
165
+ while row <= @max_scr_ln do
166
+ line = @buf[i, PER_LINE]
167
+ return unless line
168
+
169
+ @ui.goto(@shift_x, row)
170
+
171
+ hex = line.bytes.map { |x| sprintf('%02x', x) }.join(' ')
172
+ char = line.bytes.map { |x| byte_to_display_char(x) }.join
173
+
174
+ printf FMT, i, hex, char
175
+ i += PER_LINE
176
+ row += 1
177
+ end
178
+ end
179
+
180
+ def each_highlight_region
181
+ return if @hl_regions.nil?
182
+ n = @hl_regions.size
183
+ (n - 1).downto(0).each { |i|
184
+ p1 = @hl_regions[i][0]
185
+ p2 = @hl_regions[i][1]
186
+ yield i, p1, p2 unless p1.nil?
187
+ }
188
+ end
189
+
190
+ def highlight_hide
191
+ each_highlight_region { |i, p1, p2|
192
+ highlight_draw(p1, p2)
193
+ }
194
+ end
195
+
196
+ HIGHLIGHT_COLOR = [
197
+ :gray14,
198
+ :gray11,
199
+ :gray8,
200
+ :gray5,
201
+ :gray2,
202
+ ]
203
+
204
+ def highlight_show
205
+ each_highlight_region { |i, p1, p2|
206
+ @ui.bg_color = HIGHLIGHT_COLOR[i]
207
+ @ui.fg_color = :black
208
+ highlight_draw(p1, p2)
209
+ }
210
+ @ui.reset_colors
211
+ end
212
+
213
+ def highlight_draw(p1, p2)
214
+ r = row_to_scr(addr_to_row(p1))
215
+ return if r > @max_scr_ln
216
+ if r < 0
217
+ c = 0
218
+ r = 0
219
+ i = row_col_to_addr(@scroll_y, 0)
220
+ return if i >= p2
221
+ else
222
+ c = addr_to_col(p1)
223
+ i = p1
224
+ end
225
+
226
+ highlight_draw_hex(r, c, i, p2)
227
+ highlight_draw_char(r, c, i, p2)
228
+ end
229
+
230
+ def highlight_draw_hex(r, c, i, p2)
231
+ @ui.goto(col_to_col_hex(c), r)
232
+ while i < p2
233
+ v = byte_at(i)
234
+ return if v.nil?
235
+ printf('%02x ', v)
236
+ c += 1
237
+ if c >= PER_LINE
238
+ c = 0
239
+ r += 1
240
+ return if r > @max_scr_ln
241
+ @ui.goto(col_to_col_hex(c), r)
242
+ end
243
+ i += 1
244
+ end
245
+ end
246
+
247
+ def highlight_draw_char(r, c, i, p2)
248
+ @ui.goto(col_to_col_char(c), r)
249
+
250
+ while i < p2
251
+ v = byte_at(i)
252
+ return if v.nil?
253
+ print byte_to_display_char(v)
254
+ c += 1
255
+ if c >= PER_LINE
256
+ c = 0
257
+ r += 1
258
+ return if r > @max_scr_ln
259
+ @ui.goto(col_to_col_char(c), r)
260
+ end
261
+ i += 1
262
+ end
263
+ end
264
+
265
+ def byte_at(i)
266
+ v = @buf[i]
267
+ if v.nil?
268
+ nil
269
+ else
270
+ v.ord
271
+ end
272
+ end
273
+
274
+ def byte_to_display_char(x)
275
+ if x < 0x20 or x >= 0x7f
276
+ '.'
277
+ else
278
+ x.chr
279
+ end
280
+ end
281
+
282
+ def addr_to_row(addr)
283
+ addr / PER_LINE
284
+ end
285
+
286
+ def addr_to_col(addr)
287
+ addr % PER_LINE
288
+ end
289
+
290
+ def row_col_to_addr(row, col)
291
+ row * PER_LINE + col
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,242 @@
1
+ # coding: utf-8
2
+ require 'set'
3
+
4
+ require 'kaitai/struct/visualizer/version'
5
+
6
+ module Kaitai::Struct::Visualizer
7
+ class Node
8
+ attr_accessor :id
9
+ attr_reader :value
10
+ attr_reader :level
11
+ attr_reader :pos1
12
+ attr_reader :pos2
13
+ attr_reader :children
14
+ attr_accessor :parent
15
+
16
+ def initialize(tree, value, level, value_method = nil, pos1 = nil, pos2 = nil)
17
+ @tree = tree
18
+ @value = value
19
+ @level = level
20
+ @value_method = value_method
21
+
22
+ unless pos1.nil? or pos2.nil?
23
+ @pos1 = pos1
24
+ @pos2 = pos2
25
+ end
26
+
27
+ @open = false
28
+ @explored = false
29
+
30
+ @children = []
31
+ end
32
+
33
+ def add(child)
34
+ @children << child
35
+ child.parent = self
36
+ end
37
+
38
+ def open?; @open; end
39
+
40
+ def openable?
41
+ not (
42
+ @value.is_a?(Fixnum) or
43
+ @value.is_a?(String) or
44
+ @value.is_a?(Symbol) or
45
+ @value === true or
46
+ @value === false
47
+ )
48
+ end
49
+
50
+ def hex?
51
+ @value.is_a?(String)
52
+ end
53
+
54
+ def toggle
55
+ if @open
56
+ close
57
+ else
58
+ open
59
+ end
60
+ end
61
+
62
+ def open
63
+ return unless openable?
64
+ explore
65
+ @open = true if @explored
66
+ end
67
+
68
+ def close
69
+ @open = false
70
+ end
71
+
72
+ def draw(ui)
73
+ print ' ' * level
74
+ print(if @value.nil?
75
+ '[?]'
76
+ elsif open?
77
+ '[-]'
78
+ elsif openable?
79
+ '[+]'
80
+ else
81
+ '[.]'
82
+ end)
83
+ print " #{@id}"
84
+
85
+ pos = 2 * level + 4 + @id.length
86
+
87
+ if @value.is_a?(Fixnum)
88
+ print " = #{@value}"
89
+ elsif @value.is_a?(Symbol)
90
+ print " = #{@value}"
91
+ elsif @value.is_a?(String)
92
+ print ' = '
93
+ pos += 3
94
+ @str_mode = detect_str_mode unless @str_mode
95
+ max_len = @tree.tree_width - pos
96
+ case @str_mode
97
+ when :str
98
+ s = @value[0, max_len]
99
+ when :str_esc
100
+ s = @value.inspect[0, max_len]
101
+ when :hex
102
+ s = first_n_bytes_dump(@value, max_len / 3 + 1)
103
+ else
104
+ raise "Invalid str_mode: #{@str_mode.inspect}"
105
+ end
106
+ if s.length > max_len
107
+ s = s[0, max_len - 1]
108
+ s += '…'
109
+ end
110
+ print s
111
+ elsif @value === true or @value === false
112
+ print " = #{@value}"
113
+ elsif @value.is_a?(Array)
114
+ printf ' (%d = 0x%x entries)', @value.size, @value.size
115
+ end
116
+
117
+ puts
118
+ end
119
+
120
+ def first_n_bytes_dump(s, n)
121
+ i = 0
122
+ r = ''
123
+ s.each_byte { |x|
124
+ r << sprintf('%02x ', x)
125
+ i += 1
126
+ break if i >= n
127
+ }
128
+ r
129
+ end
130
+
131
+ ##
132
+ # Empirically detects a mode that would be best to show a designated string
133
+ def detect_str_mode
134
+ if @value.encoding == Encoding::ASCII_8BIT
135
+ :hex
136
+ else
137
+ :str_esc
138
+ end
139
+ end
140
+
141
+ def io
142
+ return @io if @io
143
+
144
+ if @parent.nil?
145
+ @io = @value._io
146
+ else
147
+ obj = @parent
148
+ while not obj.value.respond_to?(:_io)
149
+ obj = obj.parent
150
+ end
151
+ @io = obj.value._io
152
+ end
153
+ end
154
+
155
+ def explore
156
+ return if @explored
157
+
158
+ if @value.nil?
159
+ @value = @parent.value.send(@value_method)
160
+ end
161
+
162
+ if @value.is_a?(Fixnum) or @value.is_a?(String) or @value.is_a?(Symbol)
163
+ # do nothing else
164
+ elsif @value.is_a?(Array)
165
+ clean_id = @id[0] == '@' ? @id[1..-1] : @id
166
+ debug_el = @parent.value._debug[clean_id]
167
+ raise "Unable to get debugging aid for array: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
168
+ aid = debug_el[:arr]
169
+ raise "Unable to get debugging aid for array: #{debug_el.inspect}" unless aid
170
+
171
+ max_val_digits = @value.size.to_s.size
172
+ fmt = "%#{max_val_digits}d"
173
+
174
+ @value.each_with_index { |el, i|
175
+ n = Node.new(@tree, el, level + 1, nil, aid[i][:start], aid[i][:end])
176
+ n.id = sprintf(fmt, i)
177
+ add(n)
178
+ }
179
+ else
180
+ # Gather seq attributes
181
+ attrs = Set.new
182
+ @value.instance_variables.each { |k|
183
+ k = k.to_s
184
+ next if k =~ /^@_/
185
+ el = @value.instance_eval(k)
186
+ aid = @value._debug[k[1..-1]]
187
+ if aid
188
+ aid_s = aid[:start]
189
+ aid_e = aid[:end]
190
+ else
191
+ #raise "Unable to get debugging aid for '#{k}'"
192
+ aid_s = nil
193
+ aid_e = nil
194
+ end
195
+ n = Node.new(@tree, el, level + 1, nil, aid_s, aid_e)
196
+ n.id = k
197
+ add(n)
198
+ attrs << k.gsub(/^@/, '')
199
+ }
200
+
201
+ # Gather instances
202
+ common_meths = Set.new
203
+ @value.class.ancestors.each { |cl|
204
+ next if cl == @value.class
205
+ common_meths.merge(cl.instance_methods)
206
+ }
207
+ inst_meths = Set.new(@value.public_methods) - common_meths
208
+ inst_meths.each { |meth|
209
+ k = meth.to_s
210
+ next if k =~ /^_/ or attrs.include?(k)
211
+ n = Node.new(@tree, nil, level + 1, meth)
212
+ n.id = k
213
+ add(n)
214
+ }
215
+ end
216
+ @explored = true
217
+ end
218
+
219
+ ##
220
+ # Determine total height of an element, including all children if
221
+ # it's open and visible
222
+ def height
223
+ if @open
224
+ r = 1
225
+ @children.each { |n| r += n.height }
226
+ r
227
+ else
228
+ 1
229
+ end
230
+ end
231
+
232
+ ##
233
+ # Find out last (deepest) descendant of current node
234
+ def last_descendant
235
+ n = self
236
+ while n.open?
237
+ n = n.children.last
238
+ end
239
+ n
240
+ end
241
+ end
242
+ end