dwarftree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +113 -0
  6. data/Rakefile +2 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/dwarftree.gemspec +21 -0
  10. data/exe/dwarftree +31 -0
  11. data/lib/dwarftree/debug_info_parser.rb +146 -0
  12. data/lib/dwarftree/debug_ranges_parser.rb +24 -0
  13. data/lib/dwarftree/die/array_type.rb +4 -0
  14. data/lib/dwarftree/die/base_type.rb +5 -0
  15. data/lib/dwarftree/die/compile_unit.rb +17 -0
  16. data/lib/dwarftree/die/const_type.rb +3 -0
  17. data/lib/dwarftree/die/dwarf_procedure.rb +5 -0
  18. data/lib/dwarftree/die/enumeration_type.rb +9 -0
  19. data/lib/dwarftree/die/enumerator.rb +4 -0
  20. data/lib/dwarftree/die/formal_parameter.rb +13 -0
  21. data/lib/dwarftree/die/gnu_call_site.rb +12 -0
  22. data/lib/dwarftree/die/gnu_call_site_parameter.rb +7 -0
  23. data/lib/dwarftree/die/inlined_subroutine.rb +16 -0
  24. data/lib/dwarftree/die/label.rb +11 -0
  25. data/lib/dwarftree/die/lexical_block.rb +11 -0
  26. data/lib/dwarftree/die/member.rb +10 -0
  27. data/lib/dwarftree/die/pointer_type.rb +5 -0
  28. data/lib/dwarftree/die/restrict_type.rb +3 -0
  29. data/lib/dwarftree/die/structure_type.rb +8 -0
  30. data/lib/dwarftree/die/subprogram.rb +22 -0
  31. data/lib/dwarftree/die/subrange_type.rb +4 -0
  32. data/lib/dwarftree/die/subroutine_type.rb +5 -0
  33. data/lib/dwarftree/die/typedef.rb +6 -0
  34. data/lib/dwarftree/die/union_type.rb +7 -0
  35. data/lib/dwarftree/die/unspecified_parameters.rb +1 -0
  36. data/lib/dwarftree/die/variable.rb +17 -0
  37. data/lib/dwarftree/die/volatile_type.rb +3 -0
  38. data/lib/dwarftree/die.rb +71 -0
  39. data/lib/dwarftree/tree_filter.rb +58 -0
  40. data/lib/dwarftree/tree_visualizer.rb +69 -0
  41. data/lib/dwarftree/version.rb +3 -0
  42. data/lib/dwarftree.rb +24 -0
  43. metadata +86 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1cbf8325c9c3f5a37a6a0db7c15291ae52269ef3555e669c087f01079ca7fd6b
4
+ data.tar.gz: 5ce3c1fd1da182a3747cc6fdfad54287939e8c7ae49b4788cec65a474fd4563b
5
+ SHA512:
6
+ metadata.gz: b564269e0d418225f1fe2a66aa5e9a3589ec6307c47592d2bc4393f20693b8540149628445cad3c3addaa5f3f9eec270a75d5a455d41e4415eef587f691eee1c
7
+ data.tar.gz: 2b94b9c633e312d751fdb9a3423f0d5359628ed3d3643978564dd1a38dbbe61225ca0c5133557956cf515ee41f78d4265f051fe28f5771f62a0130fccd4ec593
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dwarftree.gemspec
4
+ gemspec
5
+
6
+ gem "rake"
7
+ gem "pry"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Takashi Kokubun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # Dwarftree
2
+
3
+ A wrapper of `objdump --dwarf=info` to visualize an object's structure and show code size
4
+
5
+ ## Installation
6
+
7
+ Prepare Ruby 2.6+, and
8
+
9
+ ```bash
10
+ $ gem install dwarftree
11
+ ```
12
+
13
+ ## Usage
14
+ First, make sure your object file has debug symbols (dwarf) and you have `objdump`.
15
+
16
+ ### Dump all entries
17
+
18
+ ```bash
19
+ $ dwarftree /tmp/_ruby_mjit_p30080u145.so
20
+ compile_unit (name: /tmp/_ruby_mjit_p16698u145.c)
21
+ base_type (byte_size: 8, encoding: 4 (float), name: double)
22
+ base_type (byte_size: 8, encoding: 5 (signed), name: long int)
23
+ ...
24
+ subprogram (name: _mjit102_sinatra_base_rb_force_encoding)
25
+ ...
26
+ lexical_block (ranges: [223864..223982, 224056..224327, 224597..224624, 224704..224720, 224768..224784, 224840..224848, 224880..224896, 224928..225059], sibling: <0x6d3a>)
27
+ variable (name: cc, decl_file: 99, decl_line: 26, type: variable, const_value: 0x55d561e7f3b0)
28
+ variable (name: cc_cme, decl_file: 99, decl_line: 27, type: variable, const_value: 0x55d561ac71a0)
29
+ lexical_block (ranges: [224103..224116, 224132..224317, 224597..224624, 224840..224848, 224928..224952, 224976..225059], sibling: <0x6c86>)
30
+ variable (name: val, decl_file: 99, decl_line: 37, type: variable, location: 0x6fb8 (location list))
31
+ variable (name: calling, decl_file: 99, decl_line: 38, type: variable, location: 0x7014 (location list))
32
+ inlined_subroutine (abstract_origin: vm_call_iseq_setup_normal)
33
+ formal_parameter (type: formal_parameter, location: 0x718a (location list), abstract_origin: local_size)
34
+ formal_parameter (type: formal_parameter, location: 0x718a (location list), abstract_origin: param_size)
35
+ ...
36
+ ```
37
+
38
+ ### Dump a single subprogram
39
+
40
+ ```bash
41
+ $ dwarftree /tmp/_ruby_mjit_p16698u145.so -S _mjit102_sinatra_base_rb_force_encoding
42
+ subprogram (name: _mjit102_sinatra_base_rb_force_encoding)
43
+ formal_parameter (name: ec, decl_file: 99, decl_line: 8, type: formal_parameter, location: 0x6c82 (location list))
44
+ formal_parameter (name: reg_cfp, decl_file: 99, decl_line: 8, type: formal_parameter, location: 0x6d8c (location list))
45
+ variable (name: stack, decl_file: 99, decl_line: 10, type: variable, location: 0x6e13 (location list))
46
+ variable (name: original_iseq, decl_file: 99, decl_line: 11, type: variable, const_value: 0x55d5616ff108)
47
+ variable (name: original_body_iseq, decl_file: 99, decl_line: 12, type: variable, const_value: 0x55d5619a74f0)
48
+ label (name: label_0, decl_file: 99, decl_line: 14, low_pc: 0x36a60)
49
+ label (name: label_1, decl_file: 99, decl_line: 24, low_pc: 0x36a78)
50
+ label (name: send_cancel, decl_file: 99, decl_line: 132, low_pc: 0x36aee)
51
+ label (name: cancel, decl_file: 99, decl_line: 150, low_pc: 0x36b17)
52
+ ...
53
+ label (name: ivar_cancel, decl_file: 99, decl_line: 138)
54
+ label (name: exivar_cancel, decl_file: 99, decl_line: 144)
55
+ lexical_block (low_pc: 0x36a74, high_pc: 0x4, sibling: <0x69e7>)
56
+ variable (name: stack_size, decl_file: 99, decl_line: 16, type: variable, const_value: 0)
57
+ variable (name: val, decl_file: 99, decl_line: 17, type: variable, location: 0x6f23 (location list))
58
+ ...
59
+ ```
60
+
61
+ ### Sort subprograms by code size
62
+
63
+ ```bash
64
+ $ dwarftree /tmp/_ruby_mjit_p16698u145.so --show-size --sort-size --die subprogram
65
+ subprogram size=12.0K (name: _mjit95_sinatra_base_rb_process_route)
66
+ subprogram size=6.4K (name: _mjit133_logger_rb_level_)
67
+ subprogram size=6.3K (name: _mjit85_rack_request_rb_POST)
68
+ subprogram size=4.9K (name: setup_parameters_complex)
69
+ subprogram size=3.9K (name: _mjit43_rack_method_override_rb_call)
70
+ subprogram size=3.6K (name: _mjit63_rack_protection_path_traversal_rb_cleanup)
71
+ subprogram size=3.6K (name: _mjit64_rack_protection_xss_header_rb_call)
72
+ subprogram size=3.5K (name: _mjit14_sinatra_base_rb_body)
73
+ subprogram size=3.2K (name: _mjit81_rack_query_parser_rb_parse_nested_query)
74
+ subprogram size=3.1K (name: _mjit56_logger_log_device_rb_set_dev)
75
+ subprogram size=3.1K (name: _mjit78_rack_request_rb_GET)
76
+ subprogram size=3.0K (name: _mjit51_logger_rb_initialize)
77
+ subprogram size=3.0K (name: _mjit143_sinatra_base_rb_invoke)
78
+ ...
79
+ ```
80
+
81
+ ### Inspect code size of inlined subroutines
82
+
83
+ ```bash
84
+ $ dwarftree /tmp/_ruby_mjit_p16698u145.so -S _mjit102_sinatra_base_rb_force_encoding --show-size --sort-size --die inlined_subroutine
85
+ subprogram size=1.2K (name: _mjit102_sinatra_base_rb_force_encoding)
86
+ inlined_subroutine size=536 (abstract_origin: vm_sendish)
87
+ inlined_subroutine size=34 (abstract_origin: mjit_exec)
88
+ inlined_subroutine size=14 (abstract_origin: vm_ci_argc)
89
+ inlined_subroutine size=8 (abstract_origin: vm_ci_packed_p)
90
+ inlined_subroutine size=6 (abstract_origin: vm_ci_flag)
91
+ inlined_subroutine size=4 (abstract_origin: VM_ENV_FLAGS_SET)
92
+ inlined_subroutine size=4 (abstract_origin: VM_ENV_FLAGS_SET)
93
+ inlined_subroutine size=342 (abstract_origin: mjit_exec)
94
+ inlined_subroutine size=29 (abstract_origin: mjit_target_iseq_p)
95
+ inlined_subroutine size=264 (abstract_origin: vm_call_iseq_setup_normal)
96
+ inlined_subroutine size=206 (abstract_origin: vm_push_frame)
97
+ inlined_subroutine size=173 (abstract_origin: rb_class_of)
98
+ inlined_subroutine size=15 (abstract_origin: RB_SPECIAL_CONST_P)
99
+ inlined_subroutine size=4 (abstract_origin: RBASIC_CLASS)
100
+ inlined_subroutine size=106 (abstract_origin: vm_splat_array)
101
+ inlined_subroutine size=29 (abstract_origin: vm_cc_valid_p)
102
+ inlined_subroutine size=4 (abstract_origin: VM_ENV_FLAGS_SET)
103
+ ```
104
+
105
+ ## Project status
106
+
107
+ It works well on my x86\_64 Ubuntu 18.04 environment, but they aren't tested in other environments.
108
+
109
+ As I originally wanted to dig into only `subprogram` and `inlined_subroutine`, other DIEs have not been formatted well yet.
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dwarftree"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/dwarftree.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ require_relative 'lib/dwarftree/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'dwarftree'
5
+ spec.version = Dwarftree::VERSION
6
+ spec.authors = ['Takashi Kokubun']
7
+ spec.email = ['takashikkbn@gmail.com']
8
+
9
+ spec.summary = %q{A wrapper of `objdump --dwarf=info` to visualize a structure of inlined subroutines}
10
+ spec.description = %q{A wrapper of `objdump --dwarf=info` to visualize a structure of inlined subroutines}
11
+ spec.homepage = 'https://github.com/k0kubun/dwarftree'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
14
+
15
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
16
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ end
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+ end
data/exe/dwarftree ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dwarftree'
4
+ require 'optparse'
5
+ require 'set'
6
+
7
+ subprograms = Set.new
8
+ dies = Set.new
9
+ show_size = false
10
+ sort_size = false
11
+
12
+ objects = OptionParser.new do |o|
13
+ o.banner = "Usage: #{$0} OBJECT"
14
+ o.on('-S', '--subprogram NAME1,NAME2,...', String, 'Show only specified subprograms and their children') do |ss|
15
+ ss.split(',').each { |s| subprograms.add(s) }
16
+ end
17
+ o.on('--die TAG1,TAG2,...', String, 'Show only specified DIEs') do |ds|
18
+ ds.split(',').each { |d| dies.add(d) }
19
+ end
20
+ o.on('--show-size', 'Show code size for DIEs which have the information') do
21
+ show_size = true
22
+ end
23
+ o.on('--sort-size', 'Show code size and sort sibling DIEs by it') do
24
+ show_size = true
25
+ sort_size = true
26
+ end
27
+ end.parse!(ARGV)
28
+
29
+ objects.each do |object|
30
+ Dwarftree.run(object, dies: dies, subprograms: subprograms, show_size: show_size, sort_size: sort_size)
31
+ end
@@ -0,0 +1,146 @@
1
+ require 'dwarftree/debug_ranges_parser'
2
+ require 'dwarftree/die'
3
+ require 'strscan'
4
+
5
+ class Dwarftree::DebugInfoParser
6
+ CommandError = Class.new(StandardError)
7
+ ParserError = Class.new(StandardError)
8
+
9
+ using Module.new {
10
+ refine StringScanner do
11
+ def scan!(pattern)
12
+ scan(pattern).tap do |result|
13
+ if result.nil?
14
+ raise ParserError.new("Expected #{pattern.inspect} but got: #{rest}")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ }
20
+
21
+ def self.parse(object)
22
+ begin
23
+ offset_ranges = Dwarftree::DebugRangesParser.parse(object)
24
+ rescue Dwarftree::DebugRangesParser::CommandError => e
25
+ raise CommandError.new(e.message)
26
+ end
27
+
28
+ cmd = ['objdump', '--dwarf=info', object]
29
+ debug_info = IO.popen(cmd, &:read)
30
+ unless $?.success?
31
+ raise CommandError.new("Failed to run: #{cmd.join(' ')}")
32
+ end
33
+ new(offset_ranges).parse(debug_info)
34
+ end
35
+
36
+ def initialize(offset_ranges)
37
+ @offset_die = {} # { 12345 => #<Dwarftree::DIE:* ...> }
38
+ @offset_ranges = offset_ranges # { 12345 => [(12345..67890), ...] }
39
+ end
40
+
41
+ # @param [String] debug_info
42
+ # @return [Array<Dwarftree::DIE::CompileUnit>]
43
+ def parse(debug_info)
44
+ compile_units = []
45
+ each_compilation_unit(debug_info) do |compilation_unit|
46
+ compile_units << parse_compilation_unit(compilation_unit)
47
+ end
48
+ compile_units
49
+ end
50
+
51
+ private
52
+
53
+ # @param [String] debug_info
54
+ def each_compilation_unit(debug_info)
55
+ compilation_units = debug_info.split(/^ Compilation Unit @ offset 0x\h+:\n/)
56
+ compilation_units.drop(1).each do |compilation_unit|
57
+ dies = compilation_unit.sub!(/\A( [^:]+: [^\n]+\n)+/, '')
58
+ if dies.nil?
59
+ raise ParserError.new("Expected Compilation Unit to have attributes but got: #{dies}")
60
+ end
61
+ yield(dies)
62
+ end
63
+ end
64
+
65
+ # @param [String] compilation_unit
66
+ def parse_compilation_unit(compilation_unit)
67
+ dies = []
68
+ each_die(compilation_unit) do |die|
69
+ if parsed = parse_die(die)
70
+ dies << parsed
71
+ end
72
+ end
73
+ dies.each do |die|
74
+ resolve_references(die)
75
+ die.freeze
76
+ end
77
+ build_tree(dies)
78
+ end
79
+
80
+ # @param [String] dies
81
+ def each_die(dies, &block)
82
+ dies.scan(/^ <\d+><\h+>:[^\n]+\n(?: <\h+> [^\n]+\n)*/, &block)
83
+ end
84
+
85
+ # @param [String] die
86
+ def parse_die(die)
87
+ scanner = StringScanner.new(die)
88
+
89
+ scanner.scan!(/ <(?<level>\d+)><(?<offset>\h+)>: Abbrev Number: (\d+ \(DW_TAG_(?<type>[^\)]+)\)|0)\n/)
90
+ level, offset, type = scanner[:level], scanner[:offset], scanner[:type]
91
+ return nil if type.nil?
92
+
93
+ attributes = {}
94
+ while scanner.scan(/ <\h+> DW_AT_(?<key>[^ ]+) *:( \([^\)]+\):)? (?<value>[^\n]+)\n/)
95
+ key, value = scanner[:key], scanner[:value]
96
+ attributes[key.to_sym] = value
97
+ end
98
+
99
+ build_die(type, level: Integer(level), offset: offset.to_i(16), attributes: attributes)
100
+ end
101
+
102
+ # @param [String] type
103
+ # @param [Integer] level
104
+ # @param [Integer] offset
105
+ # @param [Hash{ Symbol => String }] attributes
106
+ # @return [Dwarftree::DIE::*]
107
+ def build_die(type, level:, offset:, attributes:)
108
+ const = type.split('_').map { |s| s.sub(/\A\w/, &:upcase) }.join
109
+ klass = Dwarftree::DIE.const_get(const, false)
110
+ begin
111
+ die = klass.new(**attributes)
112
+ rescue ArgumentError
113
+ $stderr.puts "Caught ArgumentError on Dwarftree::DIE::#{const}.new"
114
+ raise
115
+ end
116
+ die.type = type
117
+ die.level = level
118
+ @offset_die[offset] = die
119
+ end
120
+
121
+ def resolve_references(die)
122
+ if die.respond_to?(:ranges) && die[:ranges]
123
+ die.ranges = @offset_ranges.fetch(die[:ranges].to_i(16))
124
+ end
125
+ if die.respond_to?(:abstract_origin) && die[:abstract_origin]
126
+ offset = die[:abstract_origin].match(/\A<0x(?<offset>\h+)>\z/)[:offset]
127
+ die.abstract_origin = @offset_die.fetch(offset.to_i(16))
128
+ end
129
+ end
130
+
131
+ # @param [Array<Dwarftree::DIE::*>] nodes
132
+ def build_tree(nodes)
133
+ stack = [nodes.first]
134
+ nodes.drop(1).each do |node|
135
+ while stack.last.level + 1 > node.level
136
+ stack.pop
137
+ end
138
+ if stack.last.level + 1 != node.level
139
+ raise ParserError.new("unexpected node level #{node.level} against stack #{stack.last.level}")
140
+ end
141
+ stack.last.children << node
142
+ stack.push(node)
143
+ end
144
+ stack.first
145
+ end
146
+ end
@@ -0,0 +1,24 @@
1
+ class Dwarftree::DebugRangesParser
2
+ CommandError = Class.new(StandardError)
3
+
4
+ # @param [String] object
5
+ def self.parse(object)
6
+ cmd = ['objdump', '--dwarf=Ranges', object]
7
+ debug_ranges = IO.popen(cmd, &:read)
8
+ unless $?.success?
9
+ raise CommandError.new("Failed to run: #{cmd.join(' ')}")
10
+ end
11
+ new.parse(debug_ranges)
12
+ end
13
+
14
+ # @param [String] debug_ranges
15
+ # @return [Hash{ Integer => Array<Range<Integer>> }]
16
+ def parse(debug_ranges)
17
+ offset_ranges = Hash.new { |h, k| h[k] = [] }
18
+ debug_ranges.scan(/^ \h{8} \h{16} \h{16} $/) do |line|
19
+ offset, range_beg, range_end = line.strip.split(' ')
20
+ offset_ranges[offset.to_i(16)] << (range_beg.to_i(16)..range_end.to_i(16))
21
+ end
22
+ offset_ranges
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ Dwarftree::DIE::ArrayType = Dwarftree::DIE.new(
2
+ :type,
3
+ :sibling,
4
+ )
@@ -0,0 +1,5 @@
1
+ Dwarftree::DIE::BaseType = Dwarftree::DIE.new(
2
+ :byte_size,
3
+ :encoding,
4
+ :name,
5
+ )
@@ -0,0 +1,17 @@
1
+ Dwarftree::DIE::CompileUnit = Dwarftree::DIE.new(
2
+ :producer,
3
+ :language,
4
+ :name,
5
+ :comp_dir,
6
+ :ranges,
7
+ :low_pc,
8
+ :high_pc,
9
+ :stmt_list,
10
+ :GNU_macros,
11
+ ) do
12
+ self.attributes = [:name]
13
+
14
+ def name
15
+ File.expand_path(self[:name], comp_dir)
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ Dwarftree::DIE::ConstType = Dwarftree::DIE.new(
2
+ :type,
3
+ )
@@ -0,0 +1,5 @@
1
+ Dwarftree::DIE::DwarfProcedure = Dwarftree::DIE.new(
2
+ :location,
3
+ ) do
4
+ self.attributes = []
5
+ end
@@ -0,0 +1,9 @@
1
+ Dwarftree::DIE::EnumerationType = Dwarftree::DIE.new(
2
+ :name,
3
+ :encoding,
4
+ :byte_size,
5
+ :type,
6
+ :decl_file,
7
+ :decl_line,
8
+ :sibling,
9
+ )
@@ -0,0 +1,4 @@
1
+ Dwarftree::DIE::Enumerator = Dwarftree::DIE.new(
2
+ :name,
3
+ :const_value,
4
+ )
@@ -0,0 +1,13 @@
1
+ Dwarftree::DIE::FormalParameter = Dwarftree::DIE.new(
2
+ :name,
3
+ :decl_file,
4
+ :decl_line,
5
+ :type,
6
+ :location,
7
+ :abstract_origin,
8
+ :const_value,
9
+ ) do
10
+ def abstract_origin
11
+ self[:abstract_origin]&.name
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ Dwarftree::DIE::GNUCallSite = Dwarftree::DIE.new(
2
+ :low_pc,
3
+ :ranges,
4
+ :sibling,
5
+ :abstract_origin,
6
+ :GNU_tail_call,
7
+ :GNU_call_site_target,
8
+ ) do
9
+ def abstract_origin
10
+ self[:abstract_origin]&.name
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ Dwarftree::DIE::GNUCallSiteParameter = Dwarftree::DIE.new(
2
+ :location,
3
+ :GNU_call_site_value,
4
+ :abstract_origin,
5
+ ) do
6
+ self.attributes = [:location]
7
+ end
@@ -0,0 +1,16 @@
1
+ Dwarftree::DIE::InlinedSubroutine = Dwarftree::DIE.new(
2
+ :abstract_origin,
3
+ :entry_pc,
4
+ :ranges,
5
+ :call_file,
6
+ :call_line,
7
+ :sibling,
8
+ :low_pc,
9
+ :high_pc,
10
+ ) do
11
+ self.attributes = [:abstract_origin]
12
+
13
+ def abstract_origin
14
+ self[:abstract_origin].name
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ Dwarftree::DIE::Label = Dwarftree::DIE.new(
2
+ :name,
3
+ :decl_file,
4
+ :decl_line,
5
+ :low_pc,
6
+ :abstract_origin,
7
+ ) do
8
+ def abstract_origin
9
+ self[:abstract_origin]&.name
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ Dwarftree::DIE::LexicalBlock = Dwarftree::DIE.new(
2
+ :low_pc,
3
+ :high_pc,
4
+ :ranges,
5
+ :sibling,
6
+ :abstract_origin,
7
+ ) do
8
+ def abstract_origin
9
+ self[:abstract_origin]&.type
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ Dwarftree::DIE::Member = Dwarftree::DIE.new(
2
+ :name,
3
+ :decl_file,
4
+ :decl_line,
5
+ :type,
6
+ :byte_size,
7
+ :bit_size,
8
+ :bit_offset,
9
+ :data_member_location,
10
+ )
@@ -0,0 +1,5 @@
1
+ Dwarftree::DIE::PointerType = Dwarftree::DIE.new(
2
+ :pointer_type,
3
+ :byte_size,
4
+ :type,
5
+ )
@@ -0,0 +1,3 @@
1
+ Dwarftree::DIE::RestrictType = Dwarftree::DIE.new(
2
+ :type,
3
+ )
@@ -0,0 +1,8 @@
1
+ Dwarftree::DIE::StructureType = Dwarftree::DIE.new(
2
+ :name,
3
+ :byte_size,
4
+ :decl_file,
5
+ :decl_line,
6
+ :sibling,
7
+ :declaration,
8
+ )
@@ -0,0 +1,22 @@
1
+ Dwarftree::DIE::Subprogram = Dwarftree::DIE.new(
2
+ :external,
3
+ :name,
4
+ :decl_file,
5
+ :decl_line,
6
+ :prototyped,
7
+ :type,
8
+ :low_pc,
9
+ :high_pc,
10
+ :frame_base,
11
+ :declaration,
12
+ :inline,
13
+ :sibling,
14
+ :artificial,
15
+ :noreturn,
16
+ :GNU_all_call_sites,
17
+ :linkage_name,
18
+ :abstract_origin,
19
+ :GNU_all_tail_call_sites,
20
+ ) do
21
+ self.attributes = [:name, :inline]
22
+ end
@@ -0,0 +1,4 @@
1
+ Dwarftree::DIE::SubrangeType = Dwarftree::DIE.new(
2
+ :type,
3
+ :upper_bound,
4
+ )
@@ -0,0 +1,5 @@
1
+ Dwarftree::DIE::SubroutineType = Dwarftree::DIE.new(
2
+ :prototyped,
3
+ :type,
4
+ :sibling,
5
+ )
@@ -0,0 +1,6 @@
1
+ Dwarftree::DIE::Typedef = Dwarftree::DIE.new(
2
+ :name,
3
+ :decl_file,
4
+ :decl_line,
5
+ :type,
6
+ )
@@ -0,0 +1,7 @@
1
+ Dwarftree::DIE::UnionType = Dwarftree::DIE.new(
2
+ :byte_size,
3
+ :decl_file,
4
+ :decl_line,
5
+ :sibling,
6
+ :name,
7
+ )
@@ -0,0 +1 @@
1
+ Dwarftree::DIE::UnspecifiedParameters = Dwarftree::DIE.new
@@ -0,0 +1,17 @@
1
+ Dwarftree::DIE::Variable = Dwarftree::DIE.new(
2
+ :name,
3
+ :decl_file,
4
+ :decl_line,
5
+ :type,
6
+ :external,
7
+ :declaration,
8
+ :location,
9
+ :const_value,
10
+ :artificial,
11
+ :abstract_origin,
12
+ :specification,
13
+ ) do
14
+ def abstract_origin
15
+ self[:abstract_origin]&.name
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ Dwarftree::DIE::VolatileType = Dwarftree::DIE.new(
2
+ :type,
3
+ )
@@ -0,0 +1,71 @@
1
+ # Debugging Information Entry
2
+ module Dwarftree::DIE
3
+ using Module.new {
4
+ refine Struct.singleton_class do
5
+ def new(*args, **_opts, &block)
6
+ if args.empty?
7
+ # Pseudo Struct for no-member DIE
8
+ Class.new do
9
+ def self.members
10
+ []
11
+ end
12
+
13
+ if block
14
+ class_exec(&block)
15
+ end
16
+ end
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+ }
23
+
24
+ def self.new(*args, &block)
25
+ Struct.new(*args, keyword_init: true) do
26
+ class << self
27
+ attr_accessor :attributes
28
+ end
29
+ self.attributes = members
30
+
31
+ # Not in members to avoid a conflict with DIE attributes
32
+ attr_accessor :type, :level, :children
33
+
34
+ def initialize(**)
35
+ super
36
+ self.children = []
37
+ end
38
+
39
+ if block
40
+ class_exec(&block)
41
+ end
42
+ freeze
43
+ end
44
+ end
45
+
46
+ require 'dwarftree/die/array_type'
47
+ require 'dwarftree/die/base_type'
48
+ require 'dwarftree/die/compile_unit'
49
+ require 'dwarftree/die/const_type'
50
+ require 'dwarftree/die/dwarf_procedure'
51
+ require 'dwarftree/die/enumeration_type'
52
+ require 'dwarftree/die/enumerator'
53
+ require 'dwarftree/die/formal_parameter'
54
+ require 'dwarftree/die/gnu_call_site'
55
+ require 'dwarftree/die/gnu_call_site_parameter'
56
+ require 'dwarftree/die/inlined_subroutine'
57
+ require 'dwarftree/die/label'
58
+ require 'dwarftree/die/lexical_block'
59
+ require 'dwarftree/die/member'
60
+ require 'dwarftree/die/pointer_type'
61
+ require 'dwarftree/die/restrict_type'
62
+ require 'dwarftree/die/structure_type'
63
+ require 'dwarftree/die/subprogram'
64
+ require 'dwarftree/die/subrange_type'
65
+ require 'dwarftree/die/subroutine_type'
66
+ require 'dwarftree/die/typedef'
67
+ require 'dwarftree/die/union_type'
68
+ require 'dwarftree/die/unspecified_parameters'
69
+ require 'dwarftree/die/variable'
70
+ require 'dwarftree/die/volatile_type'
71
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ class Dwarftree::TreeFilter
3
+ using Module.new {
4
+ refine Array do
5
+ def flat_map!(&block)
6
+ replace(flat_map(&block))
7
+ end
8
+ end
9
+ }
10
+
11
+ # @param [Set<String>] subprograms
12
+ # @param [Set<String>] dies
13
+ def initialize(subprograms:, dies:)
14
+ @subprograms = subprograms
15
+ @dies = dies
16
+ end
17
+
18
+ # @param [Array<Dwarftree::DIE::*>] nodes
19
+ def filter!(nodes, filter_subprogram: @subprograms.empty?)
20
+ unless @subprograms.empty?
21
+ filter_subprograms!(nodes)
22
+ end
23
+ unless @dies.empty?
24
+ filter_dies!(nodes)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ # @param [Array<Dwarftree::DIE::*>] nodes - Modified to Array<Dwarftree::DIE::Subprogram> after this call
31
+ def filter_subprograms!(nodes)
32
+ nodes.flat_map! do |node|
33
+ if matching_subprogram?(node)
34
+ node
35
+ else
36
+ filter_subprograms!(node.children)
37
+ node.children
38
+ end
39
+ end
40
+ end
41
+
42
+ def filter_dies!(nodes)
43
+ nodes.flat_map! do |node|
44
+ filter_dies!(node.children)
45
+
46
+ # Exactly-matched subprogram should not be filtered out
47
+ if matching_subprogram?(node) || @dies.include?(node.type)
48
+ node
49
+ else
50
+ node.children
51
+ end
52
+ end
53
+ end
54
+
55
+ def matching_subprogram?(node)
56
+ node.type == 'subprogram' && @subprograms.include?(node.name)
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ class Dwarftree::TreeVisualizer
2
+ KiB = 1024
3
+ MiB = KiB * 1024
4
+
5
+ # @param [TrueClass,FalseClass] show_size
6
+ # @param [TrueClass,FalseClass] sort_size
7
+ def initialize(show_size:, sort_size:)
8
+ @show_size = show_size
9
+ @sort_size = sort_size
10
+ end
11
+
12
+ # @param [Array<Dwarftree::DIE::*>] nodes
13
+ def visualize(nodes)
14
+ sort(nodes).each do |node|
15
+ visualize_node(node)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # @param [Dwarftree::DIE::*] node
22
+ def visualize_node(node, depth: 0)
23
+ size = node_code_size(node)
24
+ print "#{' ' * depth}#{node.type}"
25
+ puts "#{(" size=#{human_size(size)}" if @show_size && size)} #{node_attributes(node)}"
26
+
27
+ sort(node.children).each do |child|
28
+ visualize_node(child, depth: depth + 1)
29
+ end
30
+ end
31
+
32
+ def node_attributes(node)
33
+ attrs = node.class.attributes.map do |attr|
34
+ if value = node.public_send(attr)
35
+ "#{attr}: #{value}"
36
+ end
37
+ end
38
+ attrs.compact!
39
+
40
+ return '' if attrs.empty?
41
+ "(#{attrs.join(', ')})"
42
+ end
43
+
44
+ def node_code_size(node)
45
+ if node.respond_to?(:ranges) && node.ranges
46
+ node.ranges.map { |range| range.end - range.begin }.sum
47
+ elsif node.respond_to?(:high_pc) && node.high_pc
48
+ node.high_pc.to_i(16) # surprisingly low_pc is not needed to know code size
49
+ end
50
+ end
51
+
52
+ def human_size(size)
53
+ if size > MiB
54
+ "%.1fM" % (size.to_f / MiB)
55
+ elsif size > KiB
56
+ "%.1fK" % (size.to_f / KiB)
57
+ else
58
+ size
59
+ end
60
+ end
61
+
62
+ def sort(nodes)
63
+ if @sort_size
64
+ nodes.sort_by { |node| -(node_code_size(node) || 0) }
65
+ else
66
+ nodes
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Dwarftree
2
+ VERSION = '0.1.0'
3
+ end
data/lib/dwarftree.rb ADDED
@@ -0,0 +1,24 @@
1
+ module Dwarftree
2
+ require 'dwarftree/debug_info_parser'
3
+ require 'dwarftree/tree_filter'
4
+ require 'dwarftree/tree_visualizer'
5
+
6
+ # @param [String] object
7
+ # @param [Array<String>] dies
8
+ # @param [Array<String>] subprograms
9
+ # @param [TrueClass,FalseClass] show_size
10
+ # @param [TrueClass,FalseClass] sort_size
11
+ def self.run(object, dies:, subprograms:, show_size:, sort_size:)
12
+ begin
13
+ nodes = DebugInfoParser.parse(object)
14
+ rescue DebugInfoParser::CommandError => e
15
+ abort "ERROR: #{e.message}"
16
+ end
17
+ if nodes.empty?
18
+ abort "Debug info was not found in #{object.dump}"
19
+ end
20
+
21
+ Dwarftree::TreeFilter.new(dies: dies, subprograms: subprograms).filter!(nodes)
22
+ Dwarftree::TreeVisualizer.new(show_size: show_size, sort_size: sort_size).visualize(nodes)
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dwarftree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Takashi Kokubun
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A wrapper of `objdump --dwarf=info` to visualize a structure of inlined
14
+ subroutines
15
+ email:
16
+ - takashikkbn@gmail.com
17
+ executables:
18
+ - dwarftree
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".gitignore"
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - bin/console
28
+ - bin/setup
29
+ - dwarftree.gemspec
30
+ - exe/dwarftree
31
+ - lib/dwarftree.rb
32
+ - lib/dwarftree/debug_info_parser.rb
33
+ - lib/dwarftree/debug_ranges_parser.rb
34
+ - lib/dwarftree/die.rb
35
+ - lib/dwarftree/die/array_type.rb
36
+ - lib/dwarftree/die/base_type.rb
37
+ - lib/dwarftree/die/compile_unit.rb
38
+ - lib/dwarftree/die/const_type.rb
39
+ - lib/dwarftree/die/dwarf_procedure.rb
40
+ - lib/dwarftree/die/enumeration_type.rb
41
+ - lib/dwarftree/die/enumerator.rb
42
+ - lib/dwarftree/die/formal_parameter.rb
43
+ - lib/dwarftree/die/gnu_call_site.rb
44
+ - lib/dwarftree/die/gnu_call_site_parameter.rb
45
+ - lib/dwarftree/die/inlined_subroutine.rb
46
+ - lib/dwarftree/die/label.rb
47
+ - lib/dwarftree/die/lexical_block.rb
48
+ - lib/dwarftree/die/member.rb
49
+ - lib/dwarftree/die/pointer_type.rb
50
+ - lib/dwarftree/die/restrict_type.rb
51
+ - lib/dwarftree/die/structure_type.rb
52
+ - lib/dwarftree/die/subprogram.rb
53
+ - lib/dwarftree/die/subrange_type.rb
54
+ - lib/dwarftree/die/subroutine_type.rb
55
+ - lib/dwarftree/die/typedef.rb
56
+ - lib/dwarftree/die/union_type.rb
57
+ - lib/dwarftree/die/unspecified_parameters.rb
58
+ - lib/dwarftree/die/variable.rb
59
+ - lib/dwarftree/die/volatile_type.rb
60
+ - lib/dwarftree/tree_filter.rb
61
+ - lib/dwarftree/tree_visualizer.rb
62
+ - lib/dwarftree/version.rb
63
+ homepage: https://github.com/k0kubun/dwarftree
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.6.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.2.0.pre1
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: A wrapper of `objdump --dwarf=info` to visualize a structure of inlined subroutines
86
+ test_files: []