c_location 0.1

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.
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: