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 +19 -0
- data/README.md +0 -0
- data/ext/c_location.c +202 -0
- data/ext/extconf.rb +10 -0
- data/lib/c_location.rb +13 -0
- data/lib/c_location/resolver.rb +106 -0
- metadata +53 -0
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
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:
|