dwarftree 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +113 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dwarftree.gemspec +21 -0
- data/exe/dwarftree +31 -0
- data/lib/dwarftree/debug_info_parser.rb +146 -0
- data/lib/dwarftree/debug_ranges_parser.rb +24 -0
- data/lib/dwarftree/die/array_type.rb +4 -0
- data/lib/dwarftree/die/base_type.rb +5 -0
- data/lib/dwarftree/die/compile_unit.rb +17 -0
- data/lib/dwarftree/die/const_type.rb +3 -0
- data/lib/dwarftree/die/dwarf_procedure.rb +5 -0
- data/lib/dwarftree/die/enumeration_type.rb +9 -0
- data/lib/dwarftree/die/enumerator.rb +4 -0
- data/lib/dwarftree/die/formal_parameter.rb +13 -0
- data/lib/dwarftree/die/gnu_call_site.rb +12 -0
- data/lib/dwarftree/die/gnu_call_site_parameter.rb +7 -0
- data/lib/dwarftree/die/inlined_subroutine.rb +16 -0
- data/lib/dwarftree/die/label.rb +11 -0
- data/lib/dwarftree/die/lexical_block.rb +11 -0
- data/lib/dwarftree/die/member.rb +10 -0
- data/lib/dwarftree/die/pointer_type.rb +5 -0
- data/lib/dwarftree/die/restrict_type.rb +3 -0
- data/lib/dwarftree/die/structure_type.rb +8 -0
- data/lib/dwarftree/die/subprogram.rb +22 -0
- data/lib/dwarftree/die/subrange_type.rb +4 -0
- data/lib/dwarftree/die/subroutine_type.rb +5 -0
- data/lib/dwarftree/die/typedef.rb +6 -0
- data/lib/dwarftree/die/union_type.rb +7 -0
- data/lib/dwarftree/die/unspecified_parameters.rb +1 -0
- data/lib/dwarftree/die/variable.rb +17 -0
- data/lib/dwarftree/die/volatile_type.rb +3 -0
- data/lib/dwarftree/die.rb +71 -0
- data/lib/dwarftree/tree_filter.rb +58 -0
- data/lib/dwarftree/tree_visualizer.rb +69 -0
- data/lib/dwarftree/version.rb +3 -0
- data/lib/dwarftree.rb +24 -0
- 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
data/Gemfile
ADDED
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
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
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,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,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,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 @@
|
|
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,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
|
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: []
|