c_location 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.MIT ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Conrad Irwin <conrad.irwin@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
File without changes
data/ext/c_location.c ADDED
@@ -0,0 +1,202 @@
1
+ /* Load dladdr() function */
2
+ #include <dlfcn.h>
3
+
4
+ #include "ruby.h"
5
+
6
+ // TODO: why is it necessary to define this here? It ought to be in <dlfcn.h>
7
+ typedef struct {
8
+ const char *dli_fname; /* Pathname of shared object that contains address */
9
+ void *dli_fbase; /* Address at which shared object is loaded */
10
+ const char *dli_sname; /* Name of nearest symbol with address lower than addr */
11
+ void *dli_saddr; /* Exact address of symbol named in dli_sname */
12
+ } Dl2_info;
13
+
14
+ /* Given the pointer to a C function (called "name" for error handling)
15
+ * return the filename and offset of the compiled byte code for that function.
16
+ *
17
+ * This can then be used with a variety of Linux utilities to find more information about
18
+ * that function. If you're really lucky, and your programs have been compiled with
19
+ * debugging information (CFLAGS=-g) then this can even give you the original location of
20
+ * the source code of the method.
21
+ */
22
+ static VALUE file_and_offset(void *func, char *name)
23
+ {
24
+ Dl2_info info;
25
+ VALUE file;
26
+ VALUE offset;
27
+
28
+ if(!dladdr(func, &info)) {
29
+ rb_raise(rb_eRuntimeError, "could not find %s: %s", name, dlerror());
30
+ }
31
+
32
+ file = rb_str_new2(info.dli_fname);
33
+ offset = LL2NUM((long long)(func - info.dli_fbase));
34
+
35
+ return rb_ary_new3(2, file, offset);
36
+ }
37
+
38
+ static VALUE compiled_location(VALUE self);
39
+
40
+ void
41
+ Init_c_location()
42
+ {
43
+ VALUE rb_mCompiledLocation = rb_define_module("CompiledLocation");
44
+
45
+ rb_define_method(rb_mCompiledLocation, "compiled_location", compiled_location, 0);
46
+
47
+ rb_include_module(rb_cMethod, rb_mCompiledLocation);
48
+ rb_include_module(rb_cUnboundMethod, rb_mCompiledLocation);
49
+ }
50
+
51
+ #ifdef RUBY_19
52
+
53
+ typedef enum {
54
+ NOEX_PUBLIC = 0x00,
55
+ NOEX_NOSUPER = 0x01,
56
+ NOEX_PRIVATE = 0x02,
57
+ NOEX_PROTECTED = 0x04,
58
+ NOEX_MASK = 0x06,
59
+ NOEX_BASIC = 0x08,
60
+ NOEX_UNDEF = NOEX_NOSUPER,
61
+ NOEX_MODFUNC = 0x12,
62
+ NOEX_SUPER = 0x20,
63
+ NOEX_VCALL = 0x40,
64
+ NOEX_RESPONDS = 0x80
65
+ } rb_method_flag_t;
66
+
67
+ typedef enum {
68
+ VM_METHOD_TYPE_ISEQ,
69
+ VM_METHOD_TYPE_CFUNC,
70
+ VM_METHOD_TYPE_ATTRSET,
71
+ VM_METHOD_TYPE_IVAR,
72
+ VM_METHOD_TYPE_BMETHOD,
73
+ VM_METHOD_TYPE_ZSUPER,
74
+ VM_METHOD_TYPE_UNDEF,
75
+ VM_METHOD_TYPE_NOTIMPLEMENTED,
76
+ VM_METHOD_TYPE_OPTIMIZED, /* Kernel#send, Proc#call, etc */
77
+ VM_METHOD_TYPE_MISSING /* wrapper for method_missing(id) */
78
+ } rb_method_type_t;
79
+
80
+ typedef struct rb_method_cfunc_struct {
81
+ VALUE (*func)(ANYARGS);
82
+ int argc;
83
+ } rb_method_cfunc_t;
84
+
85
+ typedef struct rb_method_attr_struct {
86
+ ID id;
87
+ VALUE location;
88
+ } rb_method_attr_t;
89
+
90
+ typedef struct rb_iseq_struct rb_iseq_t;
91
+ typedef struct rb_method_definition_struct {
92
+ rb_method_type_t type; /* method type */
93
+ ID original_id;
94
+ union {
95
+ rb_iseq_t *iseq; /* should be mark */
96
+ rb_method_cfunc_t cfunc;
97
+ rb_method_attr_t attr;
98
+ VALUE proc; /* should be mark */
99
+ enum method_optimized_type {
100
+ OPTIMIZED_METHOD_TYPE_SEND,
101
+ OPTIMIZED_METHOD_TYPE_CALL
102
+ } optimize_type;
103
+ } body;
104
+ int alias_count;
105
+ } rb_method_definition_t;
106
+
107
+ typedef struct rb_method_entry_struct {
108
+ rb_method_flag_t flag;
109
+ char mark;
110
+ rb_method_definition_t *def;
111
+ ID called_id;
112
+ VALUE klass; /* should be mark */
113
+ } rb_method_entry_t;
114
+
115
+ #ifdef RUBY_193
116
+
117
+ struct METHOD {
118
+ VALUE recv;
119
+ VALUE rclass;
120
+ ID id;
121
+ rb_method_entry_t *me;
122
+ struct unlinked_method_entry_list_entry *ume;
123
+ };
124
+
125
+ static VALUE compiled_location(VALUE self)
126
+ {
127
+ struct METHOD *data;
128
+ VALUE name = rb_funcall(self, rb_intern("name"), 0);
129
+ name = rb_funcall(name, rb_intern("to_s"), 0);
130
+
131
+ // TODO: We're not validating that this is definitely a method struct.
132
+ // It would be nice if we could use TypedData_Get_Struct, but we don't
133
+ // have access to &method_data_type.
134
+ data = (struct METHOD *)DATA_PTR(self);
135
+
136
+ if (data->me->def->type != VM_METHOD_TYPE_CFUNC) {
137
+ return Qnil;
138
+ }
139
+
140
+ return file_and_offset(*data->me->def->body.cfunc.func, StringValueCStr(name));
141
+ }
142
+
143
+ #else /* RUBY_192 */
144
+
145
+ struct METHOD {
146
+ VALUE recv;
147
+ VALUE rclass;
148
+ ID id;
149
+ rb_method_entry_t me;
150
+ struct unlinked_method_entry_list_entry *ume;
151
+ };
152
+
153
+ static VALUE compiled_location(VALUE self)
154
+ {
155
+ struct METHOD *data;
156
+ VALUE name = rb_funcall(self, rb_intern("name"), 0);
157
+ name = rb_funcall(name, rb_intern("to_s"), 0);
158
+
159
+ // TODO: We're not validating that this is definitely a method struct.
160
+ // It would be nice if we could use TypedData_Get_Struct, but we don't
161
+ // have access to &method_data_type.
162
+ data = (struct METHOD *)DATA_PTR(self);
163
+
164
+ if (data->me.def->type != VM_METHOD_TYPE_CFUNC) {
165
+ return Qnil;
166
+ }
167
+
168
+ return file_and_offset(*data->me.def->body.cfunc.func, StringValueCStr(name));
169
+ }
170
+
171
+
172
+ #endif
173
+
174
+ #else /* RUBY18 */
175
+
176
+ #include "node.h"
177
+
178
+ /* Copy-pasted out of Ruby 1.8.7, not part of the official C API.*/
179
+ struct METHOD {
180
+ VALUE klass, rklass;
181
+ VALUE recv;
182
+ ID id, oid;
183
+ int safe_level;
184
+ NODE *body;
185
+ };
186
+
187
+ static VALUE compiled_location(VALUE self)
188
+ {
189
+ struct METHOD *data;
190
+ VALUE name = rb_funcall(self, rb_intern("name"), 0);
191
+
192
+ Data_Get_Struct(self, struct METHOD, data);
193
+
194
+ if (nd_type(data->body) != NODE_CFUNC) {
195
+ return Qnil;
196
+ }
197
+
198
+ return file_and_offset(*data->body->nd_cfnc, StringValueCStr(name));
199
+ }
200
+
201
+
202
+ #endif
data/ext/extconf.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'mkmf'
2
+
3
+ extension_name = "c_location"
4
+
5
+ $CFLAGS += " -DRUBY_19" if RUBY_VERSION =~ /1.9/
6
+ $CFLAGS += " -DRUBY_193" if RUBY_VERSION =~ /1.9.3/
7
+
8
+ dir_config(extension_name)
9
+
10
+ create_makefile(extension_name)
data/lib/c_location.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'shellwords'
2
+ require File.expand_path('../c_location/resolver', __FILE__)
3
+
4
+ module CompiledLocation
5
+ def c_location
6
+ if c = compiled_location
7
+ CompiledLocation::Resolver.new(*c).source_location
8
+ end
9
+ end
10
+
11
+ def compiled_location; end
12
+ require File.expand_path('../../ext/c_location', __FILE__)
13
+ end
@@ -0,0 +1,106 @@
1
+ module CompiledLocation
2
+ class Resolver
3
+ attr_accessor :shared_library, :offset
4
+
5
+ def initialize(shared_library, offset)
6
+ self.shared_library, self.offset = [shared_library, offset]
7
+ self.shared_library = `which ruby` if self.shared_library == 'ruby'
8
+ end
9
+
10
+ def source_location
11
+ if RUBY_PLATFORM =~ /darwin/
12
+ nm(shared_library, offset)
13
+ else
14
+ objdump(shared_library, offset)
15
+ end
16
+ end
17
+
18
+ def objdump(shared_library, offset)
19
+ command = "#{Shellwords.escape which_objdump} --dwarf=decodedline #{Shellwords.escape shared_library}"
20
+ output = run(command)
21
+
22
+ if m = output.lines.detect{ |line| line =~ /\s+0x#{offset.to_s(16)}$/ }
23
+ file, line, address = m.split(/\s+/)
24
+
25
+ absolutify(file, line.to_i(10), shared_library)
26
+ else
27
+ raise "Could not find #{shared_library}@0x#{offset.to_s(16)} using #{command.inspect}." +
28
+ "This may be because your ruby/extensions are not compiled with -g."
29
+ end
30
+ end
31
+
32
+ def nm(shared_library, offset)
33
+ command = "#{Shellwords.escape which_nm} -pa #{Shellwords.escape shared_library}"
34
+ output = run(command)
35
+
36
+ o_file = nil
37
+
38
+ output.lines.each do |line|
39
+ case line
40
+ when /OSO (.*\.o\)?)/
41
+ o_file = $1.sub(%r{(.*)/([^/]*)\((.*)\)}, '\1/\3')
42
+ when /^[01]*#{offset.to_s(16)}.*FUN (.+)/
43
+ raise "Your version of nm seems to not output OSO lines." unless o_file
44
+ return objdump_tt(o_file, $1)
45
+ end
46
+ end
47
+
48
+ raise "Could not find #{shared_library}@0x#{offset.to_s(16)} using #{command.inspect}." +
49
+ "This may be because your ruby/extensions are not compiled with -g."
50
+ end
51
+
52
+ def objdump_tt(shared_library, name)
53
+ output = run("#{Shellwords.escape which_objdump} -tT #{Shellwords.escape shared_library}")
54
+
55
+ if output.lines.detect{ |line| line =~ /^([0-9a-f]+).*\.text\s#{Regexp.escape name}$/ }
56
+ objdump(shared_library, $1.to_i(16))
57
+ else
58
+ raise "Could not find #{shared_library}##{name} using #{command.inspect}."
59
+ end
60
+ end
61
+
62
+ def which_objdump
63
+ objdump = `which gobjdump`.chomp
64
+ return objdump if $?.success?
65
+ objdump = `which objdump`.chomp
66
+ return objdump if $?.success?
67
+ objdump = `which /usr/local/bin/gobjdump`.chomp
68
+ return objdump if $?.success?
69
+
70
+ raise "You need to have `objdump` installed to use c_location. " +
71
+ "Try installing the binutils package (apt-get install binutils; brew install binutils)."
72
+ end
73
+
74
+ def which_nm
75
+ nm = `which nm`.chomp
76
+ return nm if $?.success?
77
+
78
+ raise "You need to have `nm` installed to use c_location. " +
79
+ "Try installing Xcode from the App Store. (GNU nm from binutils doesn't work unfortunately)"
80
+
81
+ end
82
+
83
+ def absolutify(file, line, shared_library)
84
+ if shared_library =~ %r{(.*/(gems|src)/[^/]*)/}
85
+ potentials = Dir["#{$1}/**/#{file}"]
86
+ elsif shared_library =~ %r{(.*/(rubies)/[^/]*)/}
87
+ potentials = Dir["#{$1.sub('/rubies/', '/src/')}/**/#{file}"]
88
+ else
89
+ potentials = Dir["./**/#{file}"]
90
+ end
91
+
92
+ if potentials.empty?
93
+ raise "Could not find the `#{file}:#{line}` that was used to build `#{shared_library}`." +
94
+ "If you know where this file is please submit a pull request"
95
+ else
96
+ [potentials.first, line]
97
+ end
98
+ end
99
+
100
+ def run(command)
101
+ `#{command}`.tap do |output|
102
+ raise "Failed to run #{command.inspect}: #{output}" unless $?.success?
103
+ end
104
+ end
105
+ end
106
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: c_location
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Conrad Irwin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-08 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Allows you to find the source location of methods written in C
15
+ email: conrad.irwin@gmail.com
16
+ executables: []
17
+ extensions:
18
+ - ext/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/c_location.rb
22
+ - lib/c_location/resolver.rb
23
+ - ext/extconf.rb
24
+ - ext/c_location.c
25
+ - README.md
26
+ - LICENSE.MIT
27
+ homepage: http://github.com/ConradIrwin/c_location
28
+ licenses:
29
+ - MIT
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.17
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: ! 'Adds a #c_location to Method and UnboundMethod objects.'
52
+ test_files: []
53
+ has_rdoc: