├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── ext └── heap_dump │ ├── extconf.rb │ ├── heap_dump.c │ ├── ruby_io.h │ └── specific │ ├── ruby-1.9.2 │ ├── fiber.h │ ├── gc_internal.h │ ├── internal_constant.h │ └── internal_typed_data.h │ ├── ruby-1.9.3 │ ├── fiber.h │ ├── gc_internal.h │ └── internal_typed_data.h │ ├── ruby-2.0.0 │ ├── fiber.h │ ├── gc_internal.h │ └── internal_typed_data.h │ └── ruby-2.0.0_preview1 │ ├── fiber.h │ ├── gc_internal.h │ └── internal_typed_data.h ├── heap_dump.gemspec └── lib ├── heap_dump.rb └── heap_dump └── version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | *.bundle 20 | *.so 21 | *.dll 22 | dump.json 23 | Makefile 24 | *.o 25 | mkmf.log -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | # Specify your gem's dependencies in heap_dump.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Vasily Fedoseyev 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HeapDump 2 | 3 | Low-level ruby heap memory dump - including data and code references, useful for finding leaks. 4 | Has no performance overhead while not active, so can be used in production environment. 5 | 6 | Originally written across ruby 1.9.2-p290 data structures. 7 | Does work on other 1.9.2s, 1.9.3, 2.0.0-preview1 and 2.0.0-preview2, but not well-tested yet(output is not proven to be as full etc.). 8 | Note that if you're running patched rubies it most likely will not work. 9 | 10 | Currently is under development and output format may differ. 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | gem 'heap_dump' 17 | 18 | And then execute: 19 | 20 | $ bundle 21 | 22 | Or install it yourself as: 23 | 24 | $ gem install heap_dump 25 | 26 | ## Usage 27 | 28 | ### Dumping heap references 29 | In your code call: 30 | 31 | ```ruby 32 | 33 | HeapDump.dump 34 | ``` 35 | 36 | this will run GC and then create a dump.json with live heap contents. 37 | Json contains one object per line, thus can be easily grepped. 38 | 39 | #### Output example/format 40 | 41 | Format is not stable yet, but looks like this: 42 | 43 | ```json 44 | 45 | [{"id":"_ROOTS_","stack_and_registers":[70313628419480,70313628419480,70313628419480,"trace",70313627751860],"classes":[70313627319820,70313628530860]} 46 | ,{"id":70365602702620,"bt":"T_ARRAY","val":[">=",70365602705060]} 47 | ,{"id":70365602847060,"bt":"T_ARRAY","val":[]} 48 | ,{"id":70365602702660,"bt":"T_DATA","type_name":"iseq","size":564,"name":"activate_spec","filename":"/Users/vasfed/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/site_ruby/1.9.1/rubygems.rb","line":485,"type":"method","refs_array_id":70365602847060,"coverage":null,"klass":70365602821240,"cref_stack":70365602848300,"defined_method_id":12672} 49 | ,{"id":70365602702680,"bt":"T_STRING","val":"activate_spec"} 50 | ,{"id":70365602821260,"bt":"T_HASH","val":{"EXEEXT":"","RUBY_SO_NAME":"ruby.1.9.1","arch":"x86_64-darwin11.2.0","bindir":70365603049640,"libdir":70365603050600,"ruby_install_name":"ruby","ruby_version":"1.9.1","rubylibprefix":70365603112080,"sitedir":70365603112440,"sitelibdir":70365603048920,"datadir":70365603049880,"vendordir":70365603112500,"vendorlibdir":70365603048800}} 51 | ,{"id":70365602712560,"bt":"T_CLASS","name":"URI::HTTPS","methods":{},"ivs":{"__classpath__":"URI::HTTPS","DEFAULT_PORT":443},"super":70365602771440} 52 | ,{"id":70365602771400,"bt":"T_CLASS","name":"Class","methods":{"build":70365602782860},"ivs":{"__attached__":70365602771440},"super":70365611597900} 53 | ,{"id":70365602717060,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70365602712440,"iseq":{"id":70365600821896,"name":"block in ","filename":"/Users/vasfed/.rvm/gems/ruby-1.9.2-p290/gems/rake-0.9.2.2/lib/rake/file_list.rb","line":743,"type":"block","refs_array_id":70365611724600,"coverage":null,"klass":null,"cref_stack":70365611799480,"defined_method_id":0}} 54 | ,{"id":70365603045160,"bt":"T_DATA","type_name":"VM/env","size":128,"refs":[]} 55 | ,{"id":70365613258980,"bt":"T_ICLASS","name":"Object","methods":{"==":"(CFUNC)",">":"(CFUNC)",">=":"(CFUNC)","<":"(CFUNC)","<=":"(CFUNC)","between?":"(CFUNC)"},"ivs":{"__classid__":"Comparable"},"super":70365613259120} 56 | ,{"id":70151795145340,"bt":"T_DATA","type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70151795224840,"iseq":{"id":70151828020360,"name":"block in subscribe","filename":"/Users/vasfed/acceptor.rb","line":91,"type":"block","refs_array_id":70151796420080,"coverage":null,"klass":null,"cref_stack":70151796421080,"defined_method_id":0}} 57 | ,{"id":70151795224840,"bt":"T_DATA","type_name":"VM/env","size":120,"refs":[70151806738200,70151796176180,"string1",null,0,70151795224840,null]} 58 | ] 59 | ``` 60 | etc. 61 | 62 | bt field is ruby builtin type name. 63 | 64 | Where available - val/refs/ivs/etc. field present with hash/array of references. 65 | Long numbers usually are object ids. 66 | 67 | ### Counting objects in heap 68 | 69 | Also heap_dump now includes an object counter, like `ObjectSpace.count_objects`, but capable of counting objects from your namespace 70 | 71 | ```ruby 72 | 73 | HeapDump.count_objects [YourNameSpace, "SomeClass"] # => json string 74 | ``` 75 | 76 | which results in something like: 77 | 78 | ```json 79 | 80 | { 81 | "total_slots": 56419, 82 | "free_slots": 12384, 83 | "basic_types": { 84 | "T_OBJECT": 1945, 85 | "T_CLASS": 931, 86 | "T_MODULE": 68, 87 | "T_FLOAT": 24, 88 | "T_STRING": 26557, 89 | "T_REGEXP": 346, 90 | "T_ARRAY": 6556, 91 | "T_HASH": 159, 92 | "T_STRUCT": 45, 93 | "T_BIGNUM": 2, 94 | "T_FILE": 6, 95 | "T_DATA": 3516, 96 | "T_MATCH": 15, 97 | "T_COMPLEX": 1, 98 | "T_RATIONAL": 10, 99 | "T_NODE": 3754, 100 | "T_ICLASS": 100 101 | }, 102 | "user_types": { 103 | "YourNameSpace::B": 2, 104 | "YourNameSpace::A": 3, 105 | "SomeClass": 1 106 | } 107 | } 108 | ``` 109 | 110 | 111 | ### Injecting into live process via GDB 112 | 113 | For long-running applications common and recommended usecase is to have some kind of custom action or signal handler in app itself that invokes dump. 114 | 115 | But also dump can be invoked from gdb. 116 | 117 | run gdb 118 | 119 | ```bash 120 | 121 | gdb `which ruby` $YOUR_PID 122 | ``` 123 | 124 | And then run commands like: 125 | 126 | ``` 127 | call rb_require("/Users/vasfed/work/heap_dump/lib/heap_dump.bundle") 128 | call heapdump_dump("mydump.json") 129 | ``` 130 | 131 | or `call heapdump_dump(0)`, filename defaults to dump.json. 132 | 133 | Note that yajl-ruby gem (and heap_dump itself) should be available to process this being injected into. 134 | Also on rare ocassions process(for example if gdb attached while a signal/gc) may crash after and even during dumping, so safer way is to embed it in advance, there's no performance overhead. 135 | 136 | Object count from gdb: `call (void)heapdump_count_objects_print("Object", "")` (null or empty string terminated list of namespace/class names) 137 | or `call (char*)heapdump_count_objects_return("Object", "")`, note that you then should free memory - `call (void)free($$)` 138 | 139 | ### Importing dump in MongoDB 140 | 141 | Dump can be imported in mongo for some map-reduce, easy script access etc. 142 | 143 | ```bash 144 | 145 | cat dump.json | sed 's/^[,\[]//;s/\]$//;s/^{"id"/{"_id"/' | mongoimport -d database_name -c collection_name --drop --type json 146 | ``` 147 | 148 | Note that even small dumps usually contain a few hundred thousands objects, so do not forget to add some indexes. 149 | 150 | 151 | ## What may leak 152 | 153 | ### Brief Ruby GC decription 154 | (brief) Ruby has mark-and-sweep GC, this means that it does not leak in traditional way when you lose some pointer and do not free memory. 155 | Ruby leaks references. But also it is different from reference counting GC, like one in python. 156 | 157 | For example, 3 objects: 158 | 159 | ``` 160 | A -> B -> C 161 | ``` 162 | let's assume that A is a global variable or is referenced in stack. In this chain C does not get freed bacause it has a reference path from global/stack. 163 | 164 | If reference to B gets deleted (for example A.b = nil): 165 | 166 | ``` 167 | A B -> C 168 | ``` 169 | C still is referenced by something, but both B and C will be freed, as there's no path. 170 | 171 | ### Examples of references 172 | 173 | Obvious references: from variables, instance variables (including class), arrays, hashes, etc. 174 | 175 | Less obvious: method closures. These are stored in T_DATAs with 'VM/env' type. 176 | Latest version of heap_dump allows to trace such references: search for a env, by it's id you can find it's owner iseq, which usually has file and line number where block/lambda/proc was created. 177 | 178 | ## Hints on finding leaks 179 | 180 | Usually it's a good practice first to detect leaking type by running several `HeapDump.count_objects` while making some load on your app and comparing results. Note objects that may be reasons for others to linger in memory according to your architecture (for example - something like "session"/"incoming connection"/"delayed job" etc.). 181 | 182 | Then make a full dump and inspect references to those objects. 183 | 184 | Note that heapdump operates only on process it has been called on, so if you have multiple workers (Unicorn/Rainbows/Passenger spawned processes etc.) - you may run into a situation when request for dump is not routed to process you're interested in. 185 | 186 | Also it may be a good idea to run dump in forked process or/and on signal: 187 | 188 | ```ruby 189 | 190 | Signal.trap('USR2') do 191 | old_pid = Process.pid 192 | fork { 193 | puts "Dumping worker #{old_pid}" 194 | require 'heap_dump' 195 | HeapDump.dump "dump_#{old_pid}.json" 196 | exit 197 | } 198 | end 199 | ``` 200 | 201 | ## Contributing 202 | 203 | 1. Fork it 204 | 2. Create your feature branch (`git checkout -b my-new-feature`) 205 | 3. Commit your changes (`git commit -am 'Added some feature'`) 206 | 4. Push to the branch (`git push origin my-new-feature`) 207 | 5. Create new Pull Request 208 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | 5 | require 'rake/extensiontask' 6 | Rake::ExtensionTask.new('heap_dump') 7 | 8 | desc "Simple dump test,just to check if extension compiles and does not segfault on simple dump" 9 | task :test => :compile do 10 | require "bundler/setup" 11 | require 'heap_dump' 12 | 13 | def some_meth 14 | fiber_var = :some_fiber_var2 15 | e = [1,2,3].each # makes enumerator, which references fiber 16 | Fiber.yield 17 | aaa 18 | end 19 | 20 | class Fiber 21 | def [](key) 22 | local_fiber_variables[key] 23 | end 24 | def []=(key,value) 25 | local_fiber_variables[key] = value 26 | end 27 | private 28 | def local_fiber_variables 29 | @local_fiber_variables ||= {} 30 | end 31 | end 32 | require 'fiber' 33 | Fiber.new{ 34 | fiber_var = :some_fiber_var 35 | Fiber.current[:some_fiber_ivar] = :ivar_cool_value 36 | some_meth 37 | Fiber.yield e 38 | fiber_var = :some_fiber_var3 39 | }.resume 40 | $some_global = "value_global" 41 | puts "Dumping...(rake)" 42 | HeapDump.verbose = true 43 | HeapDump.dump 44 | puts "Done" 45 | class A; end 46 | A.new 47 | puts HeapDump.count_objects [A] 48 | end 49 | 50 | task :default => :test -------------------------------------------------------------------------------- /ext/heap_dump/extconf.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #encoding: utf-8 3 | 4 | # autodetect ruby headers 5 | unless ARGV.any? {|arg| arg.include?('--with-ruby-include') } 6 | require 'rbconfig' 7 | bindir = RbConfig::CONFIG['bindir'] 8 | if bindir =~ %r{(^.*/\.rbenv/versions)/([^/]+)/bin$} 9 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/ruby-#{$2}" 10 | ARGV << "--with-ruby-include=#{ruby_include}" 11 | elsif bindir =~ %r{(^.*/\.rvm/rubies)/([^/]+)/bin$} 12 | ruby_include = "#{$1}/#{$2}/include/ruby-1.9.1/#{$2}" 13 | ruby_include = "#{ENV['rvm_path']}/src/#{$2}" unless File.exist?(ruby_include) 14 | ARGV << "--with-ruby-include=#{ruby_include}" 15 | end 16 | puts "Using ruby source from #{ruby_include}" 17 | end 18 | 19 | require 'mkmf' 20 | require 'debugger/ruby_core_source' 21 | 22 | def find_spec name,*requirements 23 | return Gem::Specification.find_by_name(name, *requirements) if Gem::Specification.respond_to? :find_by_name 24 | 25 | requirements = Gem::Requirement.default if requirements.empty? 26 | 27 | gem = Gem::Dependency.new(name, *requirements) 28 | matches = Gem.source_index.find_name(gem.name, gem.requirement) 29 | raise "No matching #{name} gem!" unless matches.any? 30 | matches.find { |spec| 31 | Gem.loaded_specs[gem.name] == spec 32 | } or matches.last 33 | end 34 | 35 | def find_gem_dir(name, *req) 36 | gem = find_spec(name, *req) 37 | return gem.gem_dir if gem.respond_to? :gem_dir 38 | gem.full_gem_path 39 | end 40 | 41 | 42 | gemspec = File.expand_path(File.join(File.dirname(__FILE__), '../../heap_dump.gemspec')) 43 | spec = instance_eval(File.read(gemspec), gemspec).dependencies.find{|d|d.name == 'yajl-ruby'} 44 | #$defs.push(format("-DREQUIRED_YAJL_VERSION=\\"%s\\"", spec.requirement)) #does not work in this form :( 45 | 46 | yajl = find_gem_dir(spec.name, spec.requirement) 47 | find_header('api/yajl_gen.h', File.join(yajl, 'ext', 'yajl')) 48 | 49 | specific = RUBY_VERSION 50 | 51 | # here other specific headers may be injected, for example - patched releases etc. 52 | if RUBY_VERSION == '2.0.0' && RUBY_REVISION == 37411 53 | specific = "2.0.0_preview1" 54 | end 55 | 56 | unless find_header("gc_internal.h", File.join(File.dirname(__FILE__),'specific', "ruby-#{specific}")) && have_header("gc_internal.h") 57 | raise "Do not have internal structs for your ruby version" 58 | end 59 | 60 | hdrs = proc { 61 | res = %w{ 62 | vm_core.h 63 | iseq.h 64 | node.h 65 | method.h 66 | }.all?{|hdr| have_header(hdr)} 67 | # atomic.h 68 | # constant.h 69 | 70 | #optional: 71 | %w{ 72 | constant.h 73 | internal.h 74 | gc.h 75 | }.each{|h| have_header(h)} 76 | 77 | have_struct_member("rb_iseq_t", "filename", "vm_core.h") 78 | have_struct_member("rb_binding_t", "filename", "vm_core.h") 79 | have_struct_member("rb_control_frame_t", "bp", "vm_core.h") 80 | have_struct_member("rb_thread_t", "thrown_errinfo", "vm_core.h") 81 | have_struct_member("rb_event_hook_t", "data", "vm_core.h") 82 | 83 | 84 | 85 | have_struct_member("rb_iseq_t", "location", "vm_core.h") 86 | #have_struct_member("rb_iseq_location_t", "filename", "vm_core.h") 87 | have_struct_member("rb_block_t", "klass", "vm_core.h") 88 | have_struct_member("rb_block_t", "lfp", "vm_core.h") 89 | 90 | res 91 | } 92 | 93 | dir_config("ruby") # allow user to pass in non-standard core include directory 94 | 95 | if ENV['DEBUG'] 96 | CONFIG['debugflags'] << ' -ggdb3 -O0' 97 | end 98 | 99 | if !Debugger::RubyCoreSource::create_makefile_with_core(hdrs, "heap_dump") 100 | STDERR.print("Makefile creation failed\n") 101 | STDERR.print("*************************************************************\n\n") 102 | STDERR.print(" NOTE: If your headers were not found, try passing\n") 103 | STDERR.print(" --with-ruby-include=PATH_TO_HEADERS \n\n") 104 | STDERR.print("*************************************************************\n\n") 105 | exit(1) 106 | end 107 | -------------------------------------------------------------------------------- /ext/heap_dump/heap_dump.c: -------------------------------------------------------------------------------- 1 | #include "ruby.h" 2 | #include "ruby/encoding.h" 3 | #include 4 | #include 5 | 6 | 7 | #ifdef HAVE_CONSTANT_H 8 | //have this in 1.9.3 9 | #include "constant.h" 10 | #else 11 | #include "internal_constant.h" 12 | #endif 13 | 14 | #include "node.h" 15 | #include "vm_core.h" 16 | // #include "atomic.h" 17 | #include "iseq.h" 18 | 19 | #ifdef HAVE_GC_H 20 | #include "gc.h" 21 | #endif 22 | 23 | #ifdef HAVE_INTERNAL_H 24 | #include "internal.h" 25 | #else 26 | #define RCLASS_EXT(c) (RCLASS(c)->ptr) 27 | #endif 28 | 29 | #define NODE_OP_ASGN2_ARG NODE_LAST + 1 30 | 31 | #ifndef FALSE 32 | # define FALSE 0 33 | #elif FALSE 34 | # error FALSE must be false 35 | #endif 36 | #ifndef TRUE 37 | # define TRUE 1 38 | #elif !TRUE 39 | # error TRUE must be true 40 | #endif 41 | 42 | #include "method.h" 43 | 44 | #include "ruby_io.h" // need rb_io_t 45 | 46 | #include "api/yajl_gen.h" 47 | 48 | #ifndef RUBY_VM 49 | #error No RUBY_VM, old rubies not supported 50 | #endif 51 | 52 | // simple test - rake compile && bundle exec ruby -e 'require "heap_dump"; HeapDump.dump' 53 | 54 | #include 55 | 56 | #ifdef HAVE_GC_INTERNAL_H 57 | #include "gc_internal.h" 58 | #else 59 | #error No internal gc header for your ruby 60 | //TODO: just do not dump something? 61 | #endif 62 | 63 | #include "fiber.h" 64 | #include "internal_typed_data.h" 65 | 66 | 67 | static VALUE rb_mHeapDumpModule; 68 | 69 | static ID classid; 70 | 71 | //shortcuts to yajl 72 | #define YAJL ctx->yajl 73 | #define yg_string(str,len) yajl_gen_string(YAJL, (const unsigned char *)(str), (unsigned int)(len)) 74 | #define yg_cstring(str) yg_string(str, strlen(str)) 75 | #define yg_rstring(str) yg_string(RSTRING_PTR(str), RSTRING_LEN(str)) 76 | #define yg_int(i) yajl_gen_integer(YAJL, i) 77 | #define yg_double(d) (yajl_gen_double(YAJL, d)==yajl_gen_invalid_number? yg_cstring("inf|NaN") : true) 78 | #define yg_null() yajl_gen_null(YAJL) 79 | #define yg_bool(b) yajl_gen_bool(YAJL, b); 80 | 81 | #define yg_funcaddr(addr) yg_funcaddr_real(ctx, addr) 82 | 83 | //#define yg_id(obj) yg_int(NUM2LONG(rb_obj_id(obj))) 84 | #define yg_id(obj) yg_id1(obj,ctx) 85 | 86 | 87 | #define ygh_id(key,obj) {yg_cstring(key); yg_id(obj);} 88 | #define ygh_int(key,i) {yg_cstring(key); yg_int((long int)(i));} 89 | #define ygh_double(key,d) {yg_cstring(key); yg_double(d);} 90 | #define ygh_string(key,str,len) {yg_cstring(key); yg_string(str,len);} 91 | #define ygh_cstring(key,str) {yg_cstring(key); yg_cstring(str);} 92 | #define ygh_rstring(key,str) {yg_cstring(key); yg_rstring(str);} 93 | 94 | #define yg_map() yajl_gen_map_open(YAJL); 95 | #define yg_map_end() yajl_gen_map_close(YAJL); 96 | #define yg_array() yajl_gen_array_open(YAJL); 97 | #define yg_array_end() yajl_gen_array_close(YAJL); 98 | 99 | 100 | // context for objectspace_walker callback 101 | typedef struct walk_ctx { 102 | int walker_called; 103 | int live_objects; 104 | FILE* file; 105 | 106 | yajl_gen yajl; 107 | } walk_ctx_t; 108 | 109 | static void flush_yajl(walk_ctx_t *ctx){ 110 | const unsigned char* buf; 111 | unsigned int len; 112 | if(yajl_gen_get_buf(ctx->yajl, &buf, &len) == yajl_gen_status_ok){ 113 | fwrite(buf, len, 1, ctx->file); 114 | yajl_gen_clear(ctx->yajl); 115 | } 116 | } 117 | 118 | static inline int is_in_heap(void *ptr, void* osp); 119 | 120 | static inline const char* rb_type_str(int type){ 121 | switch(type){ 122 | #define T(t) case t: return #t; 123 | T(T_NONE); T(T_NIL); 124 | T(T_OBJECT); T(T_CLASS); T(T_ICLASS); T(T_MODULE); 125 | T(T_SYMBOL); T(T_STRING); T(T_REGEXP); T(T_MATCH); 126 | T(T_ARRAY); T(T_HASH); T(T_STRUCT); 127 | 128 | T(T_FILE); 129 | T(T_FIXNUM); T(T_BIGNUM); T(T_FLOAT); T(T_RATIONAL); T(T_COMPLEX); 130 | 131 | T(T_TRUE); T(T_FALSE); 132 | T(T_DATA); 133 | T(T_UNDEF); 134 | T(T_NODE); // code? 135 | T(T_ZOMBIE); 136 | #undef T 137 | default: 138 | return "_unknown_type_"; 139 | } 140 | } 141 | 142 | #define true 1 143 | #define false 0 144 | 145 | static void yg_funcaddr_real(walk_ctx_t* ctx, void* addr){ 146 | Dl_info info; 147 | if(dladdr(addr, &info) && info.dli_sname){ 148 | yg_cstring(info.dli_sname); 149 | } else { 150 | yg_cstring("(unknown)"); 151 | } 152 | } 153 | 154 | //FIXME: handle non-ids? 155 | static void yg_id1(VALUE obj, walk_ctx_t* ctx){ 156 | if(!obj) { 157 | yajl_gen_null(ctx->yajl); 158 | return; 159 | } 160 | if (IMMEDIATE_P(obj)) { 161 | if (FIXNUM_P(obj)) { 162 | yg_int(FIX2LONG(obj)); 163 | return; 164 | } 165 | if (obj == Qtrue){ yajl_gen_bool(ctx->yajl, true); return; } 166 | if (SYMBOL_P(obj)) { 167 | yg_cstring(rb_id2name(SYM2ID(obj))); 168 | return; 169 | } 170 | if (obj == Qundef) { yg_cstring("(undef)"); return; } 171 | 172 | yg_cstring("(unknown)"); 173 | return; 174 | } else /*non-immediate*/ { 175 | if (!RTEST(obj)) { 176 | if (obj == Qnil){ 177 | yajl_gen_null(ctx->yajl); 178 | return; 179 | } 180 | if (obj == Qfalse) { 181 | yajl_gen_bool(ctx->yajl, false); 182 | return; 183 | } 184 | } 185 | } 186 | 187 | if(BUILTIN_TYPE(obj) == T_STRING && (!(RBASIC(obj)->flags & RSTRING_NOEMBED))){ 188 | //embedded string 189 | if(rb_enc_get_index(obj) == rb_usascii_encindex()) 190 | yg_rstring(obj); 191 | else{ 192 | //FIXME: convert encoding/safe syms etc? 193 | //yg_cstring("(encoded string)"); 194 | yg_rstring(obj); 195 | } 196 | return; 197 | } 198 | 199 | yg_int(NUM2LONG(rb_obj_id(obj))); 200 | } 201 | 202 | const char* node_type_name(const NODE* obj){ 203 | #define N(n) case NODE_##n: return #n; 204 | switch(nd_type(obj)){ 205 | N(ALIAS) 206 | #ifdef HAVE_NODE_ALLOCA 207 | N(ALLOCA) 208 | #endif 209 | N(AND) N(ARGS) N(ARGSCAT) N(ARGSPUSH) N(ARRAY) N(ATTRASGN) N(BACK_REF) N(BEGIN) N(BLOCK) N(BLOCK_ARG) N(BLOCK_PASS) N(BMETHOD) N(BREAK) 210 | N(CALL) N(CASE) N(CDECL) N(CLASS) N(COLON2) N(COLON3) N(CONST) N(CVAR) N(CVASGN) N(CVDECL) N(DASGN) N(DASGN_CURR) N(DEFINED) N(DEFN) 211 | N(DEFS) N(DOT2) N(DOT3) N(DREGX) N(DREGX_ONCE) N(DSTR) N(DSYM) N(DVAR) N(DXSTR) N(ENSURE) N(EVSTR) N(FALSE) N(FCALL) N(FLIP2) N(FLIP3) 212 | N(FOR) N(GASGN) N(GVAR) N(HASH) N(IASGN) N(IF) N(IFUNC) N(ITER) N(IVAR) N(LASGN) N(LIT) N(LVAR) N(MASGN) N(MATCH) N(MATCH2) N(MATCH3) 213 | N(MEMO) N(MODULE) N(NEXT) N(NIL) N(NTH_REF) N(OPT_N) N(OP_ASGN1) N(OP_ASGN2) N(OP_ASGN2_ARG) N(OP_ASGN_AND) N(OP_ASGN_OR) N(OR) N(POSTEXE) 214 | N(REDO) N(RESBODY) N(RESCUE) N(RETRY) N(RETURN) N(SCLASS) N(SCOPE) N(SELF) N(SPLAT) N(STR) N(SUPER) N(TO_ARY) N(TRUE) N(UNDEF) N(UNTIL) 215 | N(VALIAS) N(VCALL) N(WHEN) N(WHILE) N(XSTR) N(YIELD) N(ZARRAY) N(ZSUPER) N(LAST) 216 | default: 217 | return "unknown"; 218 | }; 219 | #undef N 220 | } 221 | 222 | 223 | static void dump_node_refs(NODE* obj, walk_ctx_t* ctx){ 224 | switch (nd_type(obj)) { 225 | case NODE_IF: /* 1,2,3 */ 226 | case NODE_FOR: 227 | case NODE_ITER: 228 | case NODE_WHEN: 229 | case NODE_MASGN: 230 | case NODE_RESCUE: 231 | case NODE_RESBODY: 232 | case NODE_CLASS: 233 | case NODE_BLOCK_PASS: 234 | //gc_mark(objspace, (VALUE)obj->as.node.u2.node, lev); 235 | yg_id((VALUE)obj->u2.node); 236 | /* fall through */ 237 | case NODE_BLOCK: /* 1,3 */ 238 | case NODE_OPTBLOCK: 239 | case NODE_ARRAY: 240 | case NODE_DSTR: 241 | case NODE_DXSTR: 242 | case NODE_DREGX: 243 | case NODE_DREGX_ONCE: 244 | case NODE_ENSURE: 245 | case NODE_CALL: 246 | case NODE_DEFS: 247 | case NODE_OP_ASGN1: 248 | //gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); 249 | yg_id((VALUE)obj->u1.node); 250 | /* fall through */ 251 | case NODE_SUPER: /* 3 */ 252 | case NODE_FCALL: 253 | case NODE_DEFN: 254 | case NODE_ARGS_AUX: 255 | //ptr = (VALUE)obj->as.node.u3.node; 256 | yg_id((VALUE)obj->u3.node); 257 | return; //goto again; 258 | 259 | case NODE_WHILE: /* 1,2 */ 260 | case NODE_UNTIL: 261 | case NODE_AND: 262 | case NODE_OR: 263 | case NODE_CASE: 264 | case NODE_SCLASS: 265 | case NODE_DOT2: 266 | case NODE_DOT3: 267 | case NODE_FLIP2: 268 | case NODE_FLIP3: 269 | case NODE_MATCH2: 270 | case NODE_MATCH3: 271 | case NODE_OP_ASGN_OR: 272 | case NODE_OP_ASGN_AND: 273 | case NODE_MODULE: 274 | case NODE_ALIAS: 275 | case NODE_VALIAS: 276 | case NODE_ARGSCAT: 277 | //gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); 278 | yg_id((VALUE)obj->u1.node); 279 | /* fall through */ 280 | case NODE_GASGN: /* 2 */ 281 | case NODE_LASGN: 282 | case NODE_DASGN: 283 | case NODE_DASGN_CURR: 284 | case NODE_IASGN: 285 | case NODE_IASGN2: 286 | case NODE_CVASGN: 287 | case NODE_COLON3: 288 | case NODE_OPT_N: 289 | case NODE_EVSTR: 290 | case NODE_UNDEF: 291 | case NODE_POSTEXE: 292 | //ptr = (VALUE)obj->as.node.u2.node; 293 | yg_id((VALUE)obj->u2.node); 294 | return; //goto again; 295 | 296 | case NODE_HASH: /* 1 */ 297 | case NODE_LIT: 298 | case NODE_STR: 299 | case NODE_XSTR: 300 | case NODE_DEFINED: 301 | case NODE_MATCH: 302 | case NODE_RETURN: 303 | case NODE_BREAK: 304 | case NODE_NEXT: 305 | case NODE_YIELD: 306 | case NODE_COLON2: 307 | case NODE_SPLAT: 308 | case NODE_TO_ARY: 309 | //ptr = (VALUE)obj->as.node.u1.node; 310 | yg_id((VALUE)obj->u1.node); 311 | return; //goto again; 312 | 313 | case NODE_SCOPE: /* 2,3 */ //ANN("format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body"); 314 | //actually this is not present in live ruby 1.9+ 315 | if(obj->nd_tbl){ 316 | ID *tbl = RNODE(obj)->nd_tbl; 317 | unsigned long i = 0, size = tbl[0]; 318 | tbl++; 319 | for (; i < size; i++) { 320 | //TODO: dump local var names? 321 | // rb_id2name(tbl[i])... 322 | //yg_id(tbl[i]); //FIXME: these are ids, not values 323 | } 324 | } 325 | case NODE_CDECL: 326 | case NODE_OPT_ARG: 327 | //gc_mark(objspace, (VALUE)obj->as.node.u3.node, lev); 328 | //ptr = (VALUE)obj->as.node.u2.node; 329 | //goto again; 330 | yg_id((VALUE)obj->u3.node); 331 | yg_id((VALUE)obj->u2.node); 332 | return; 333 | 334 | case NODE_ARGS: /* custom */ 335 | #if 0 336 | //RUBY 1.9.3 337 | { 338 | struct rb_args_info *args = obj->u3.args; 339 | if (args) { 340 | if (args->pre_init) yg_id((VALUE)args->pre_init); //gc_mark(objspace, (VALUE)args->pre_init, lev); 341 | if (args->post_init) yg_id((VALUE)args->post_init); //gc_mark(objspace, (VALUE)args->post_init, lev); 342 | if (args->opt_args) yg_id((VALUE)args->opt_args); //gc_mark(objspace, (VALUE)args->opt_args, lev); 343 | if (args->kw_args) yg_id((VALUE)args->kw_args); //gc_mark(objspace, (VALUE)args->kw_args, lev); 344 | if (args->kw_rest_arg) yg_id((VALUE)args->kw_rest_arg); //gc_mark(objspace, (VALUE)args->kw_rest_arg, lev); 345 | } 346 | } 347 | //ptr = (VALUE)obj->as.node.u2.node; 348 | yg_id(obj->u2.node); 349 | //goto again; 350 | #endif 351 | yg_id((VALUE)obj->u1.node); 352 | return; 353 | 354 | case NODE_ZARRAY: /* - */ 355 | case NODE_ZSUPER: 356 | case NODE_VCALL: 357 | case NODE_GVAR: 358 | case NODE_LVAR: 359 | case NODE_DVAR: 360 | case NODE_IVAR: 361 | case NODE_CVAR: 362 | case NODE_NTH_REF: 363 | case NODE_BACK_REF: 364 | case NODE_REDO: 365 | case NODE_RETRY: 366 | case NODE_SELF: 367 | case NODE_NIL: 368 | case NODE_TRUE: 369 | case NODE_FALSE: 370 | case NODE_ERRINFO: 371 | case NODE_BLOCK_ARG: 372 | break; 373 | case NODE_ALLOCA: 374 | //mark_locations_array(objspace, (VALUE*)obj->as.node.u1.value, obj->as.node.u3.cnt); : 375 | { 376 | VALUE* x = (VALUE*)obj->u1.value; 377 | unsigned long n = obj->u3.cnt; 378 | while (n--) { 379 | //v = *x; 380 | // if (is_in_heap((void *)v), objspace) { 381 | // //gc_mark(objspace, v, 0); 382 | yg_id(*x); 383 | // } 384 | x++; 385 | } 386 | } 387 | //ptr = (VALUE)obj->as.node.u2.node; 388 | yg_id((VALUE)obj->u2.node); 389 | //goto again; 390 | return; 391 | 392 | case NODE_MEMO: 393 | if(is_in_heap(NULL, obj->u1.node)) 394 | yg_id((VALUE)obj->u1.node); 395 | break; 396 | 397 | //not implemented: 398 | 399 | case NODE_CONST: 400 | //no ref, just id 401 | // if(n->nd_vid == 0)return Qfalse; 402 | // else if(n->nd_vid == 1)return Qtrue; 403 | // else return ID2SYM(n->nd_vid); 404 | break; 405 | case NODE_ATTRASGN: 406 | //FIXME: may hold references! 407 | break; 408 | 409 | //iteration func - blocks,procs,lambdas etc: 410 | case NODE_IFUNC: //NEN_CFNC, NEN_TVAL, NEN_STATE? / u2 seems to be data for func(context?) 411 | { 412 | //find in symbol table, if present: 413 | yg_funcaddr(obj->nd_cfnc); 414 | } 415 | if(is_in_heap(obj->u2.node, 0)){ 416 | //TODO: do we need to dump it inline? 417 | yg_id((VALUE)obj->u2.node); 418 | } 419 | if(is_in_heap( (void*)obj->nd_aid, 0)){ 420 | yg_id(obj->nd_aid); 421 | } 422 | break; 423 | 424 | //empty: 425 | case NODE_BEGIN: break; 426 | default: /* unlisted NODE */ 427 | //FIXME: check pointers! 428 | {} 429 | // if (is_in_heap(obj->as.node.u1.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); } 430 | // if (is_in_heap(obj->as.node.u2.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u2.node, lev); } 431 | // if (is_in_heap(obj->as.node.u3.node, objspace)) { gc_mark(objspace, (VALUE)obj->as.node.u3.node, lev); } 432 | 433 | //yg_id((VALUE)obj->u1.node); 434 | //yg_id((VALUE)obj->u2.node); 435 | //yg_id((VALUE)obj->u3.node); 436 | } 437 | } 438 | 439 | static inline void dump_node(NODE* obj, walk_ctx_t *ctx){ 440 | ygh_int("nd_type", nd_type(obj)); 441 | ygh_cstring("nd_type_str", node_type_name(obj)); 442 | 443 | yg_cstring("refs"); 444 | yajl_gen_array_open(ctx->yajl); 445 | dump_node_refs(obj, ctx); 446 | yajl_gen_array_close(ctx->yajl); 447 | } 448 | 449 | static int 450 | dump_keyvalue(st_data_t key, st_data_t value, walk_ctx_t *ctx){ 451 | if(!key || (VALUE)key == Qnil){ 452 | yg_cstring("___null_key___"); //TODO: just ""? 453 | } else { 454 | //TODO: keys must be strings 455 | const int type = TYPE(key); 456 | if(type == T_SYMBOL || type == T_STRING && (!(RBASIC(key)->flags & RSTRING_NOEMBED))) 457 | yg_id((VALUE)key); 458 | else 459 | { 460 | char buf[128]; 461 | buf[sizeof(buf)-1] = 0; 462 | switch(type){ 463 | case T_FIXNUM: 464 | snprintf(buf, sizeof(buf)-1, "%ld", FIX2LONG(key)); 465 | break; 466 | case T_FLOAT: 467 | snprintf(buf, sizeof(buf)-1, "%lg", NUM2DBL(key)); 468 | break; 469 | default: 470 | snprintf(buf, sizeof(buf)-1, "__id_%ld", NUM2LONG(rb_obj_id(key))); 471 | break; 472 | } 473 | yg_cstring(buf); 474 | } 475 | } 476 | yg_id((VALUE)value); 477 | return ST_CONTINUE; 478 | } 479 | 480 | static void dump_hash(VALUE obj, walk_ctx_t* ctx){ 481 | yg_cstring("val"); 482 | yajl_gen_map_open(ctx->yajl); 483 | if(RHASH_SIZE(obj) > 0){ 484 | //TODO: mark keys and values separately? 485 | st_foreach(RHASH(obj)->ntbl, dump_keyvalue, (st_data_t)ctx); 486 | } 487 | yajl_gen_map_close(ctx->yajl); 488 | } 489 | 490 | static void dump_method_definition_as_value(const rb_method_definition_t *def, walk_ctx_t *ctx){ 491 | if (!def) { 492 | yajl_gen_null(ctx->yajl); 493 | return; 494 | } 495 | 496 | switch (def->type) { 497 | case VM_METHOD_TYPE_ISEQ: 498 | yg_id(def->body.iseq->self); 499 | break; 500 | case VM_METHOD_TYPE_CFUNC: yg_cstring("(CFUNC)"); break; 501 | case VM_METHOD_TYPE_ATTRSET: 502 | case VM_METHOD_TYPE_IVAR: 503 | yg_id(def->body.attr.location); 504 | break; 505 | case VM_METHOD_TYPE_BMETHOD: 506 | yg_id(def->body.proc); 507 | break; 508 | case VM_METHOD_TYPE_ZSUPER: yg_cstring("(ZSUPER)"); break; 509 | case VM_METHOD_TYPE_UNDEF: yg_cstring("(UNDEF)"); break; 510 | case VM_METHOD_TYPE_NOTIMPLEMENTED: yg_cstring("(NOTIMP)"); break; 511 | case VM_METHOD_TYPE_OPTIMIZED: /* Kernel#send, Proc#call, etc */ yg_cstring("(OPTIMIZED)"); break; 512 | case VM_METHOD_TYPE_MISSING: yg_cstring("(MISSING)"); break; 513 | default: 514 | yajl_gen_null(ctx->yajl); 515 | break; 516 | } 517 | } 518 | 519 | static int dump_method_entry_i(ID key, const rb_method_entry_t *me, st_data_t data){ 520 | walk_ctx_t *ctx = (void*)data; 521 | if(key == ID_ALLOCATOR) { 522 | yg_cstring("___allocator___"); 523 | } else { 524 | yg_cstring(rb_id2name(key)); 525 | } 526 | 527 | //gc_mark(objspace, me->klass, lev);? 528 | dump_method_definition_as_value(me->def, ctx); 529 | return ST_CONTINUE; 530 | } 531 | 532 | static int dump_iv_entry(ID key, VALUE value, walk_ctx_t *ctx){ 533 | const char* key_str = rb_id2name(key); 534 | if(key_str) 535 | yg_cstring(key_str); 536 | else{ 537 | // cannot use yg_null() - keys must be strings 538 | //TODO: just ""? 539 | yg_cstring("___null_key___"); 540 | } 541 | yg_id(value); 542 | return ST_CONTINUE; 543 | } 544 | 545 | static int dump_const_entry_i(ID key, const rb_const_entry_t *ce, walk_ctx_t *ctx){ 546 | VALUE value = ce->value; 547 | yg_cstring(rb_id2name(key)); 548 | yg_id(value); 549 | return ST_CONTINUE; 550 | } 551 | 552 | static const char* iseq_type(VALUE type){ 553 | switch(type){ 554 | case ISEQ_TYPE_TOP: return "top"; 555 | case ISEQ_TYPE_METHOD: return "method"; 556 | case ISEQ_TYPE_BLOCK: return "block"; 557 | case ISEQ_TYPE_CLASS: return "class"; 558 | case ISEQ_TYPE_RESCUE: return "rescue"; 559 | case ISEQ_TYPE_ENSURE: return "ensure"; 560 | case ISEQ_TYPE_EVAL: return "eval"; 561 | case ISEQ_TYPE_MAIN: return "main"; 562 | case ISEQ_TYPE_DEFINED_GUARD: return "defined_guard"; 563 | } 564 | return "unknown_iseq"; 565 | } 566 | 567 | static void dump_iseq(const rb_iseq_t* iseq, walk_ctx_t *ctx){ 568 | int i; 569 | #ifdef HAVE_RB_ISEQ_T_FILENAME 570 | if(iseq->name) ygh_rstring("name", iseq->name); 571 | if(iseq->filename) ygh_rstring("filename", iseq->filename); 572 | ygh_int("line", FIX2LONG(iseq->line_no)); 573 | #else 574 | #ifdef HAVE_RB_ISEQ_T_LOCATION 575 | if(iseq->location.label) ygh_rstring("name", iseq->location.label); 576 | if(iseq->location.path) ygh_rstring("filename", iseq->location.path); 577 | //base_label usually(always?)==label 578 | // if(iseq->location.base_label) ygh_rstring("base_label", iseq->location.base_label); 579 | ygh_int("line", FIX2LONG(iseq->location.first_lineno)); 580 | #endif 581 | #endif 582 | 583 | //if(iseq->type != 25116) //also 28 in mark_ary 584 | ygh_cstring("type", iseq_type(iseq->type)); 585 | //see isec.c: iseq_data_to_ary(rb_iseq_t* ) 586 | 587 | //28 is what? 588 | ygh_id("refs_array_id", iseq->mark_ary); 589 | 590 | 591 | ygh_id("coverage", iseq->coverage); 592 | ygh_id("klass", iseq->klass); 593 | ygh_id("cref_stack", (VALUE)iseq->cref_stack); //NODE* 594 | 595 | yg_cstring("defined_method_id"); 596 | if(iseq->defined_method_id && iseq->defined_method_id != ID_ALLOCATOR){ 597 | yg_cstring(rb_id2name(iseq->defined_method_id)); 598 | } else { 599 | yg_int(iseq->defined_method_id); 600 | } 601 | 602 | if (iseq->compile_data != 0) { 603 | struct iseq_compile_data *const compile_data = iseq->compile_data; 604 | ygh_id("cd_marks_ary", compile_data->mark_ary); 605 | ygh_id("cd_err_info", compile_data->err_info); 606 | ygh_id("cd_catch_table_ary", compile_data->catch_table_ary); 607 | } 608 | 609 | if(iseq->local_table_size > 0){ 610 | yg_cstring("local_table"); 611 | yg_array(); 612 | for(i = 0; i < iseq->local_table_size; i++){ 613 | const char* name = rb_id2name(iseq->local_table[i]); 614 | if(name){ 615 | yg_cstring(name); 616 | } else { 617 | yg_cstring("(unnamed)"); 618 | } 619 | } 620 | yg_array_end(); 621 | } 622 | } 623 | 624 | static void dump_block(const rb_block_t* block, walk_ctx_t *ctx){ 625 | // VALUE self; /* share with method frame if it's only block */ 626 | // VALUE *lfp; /* share with method frame if it's only block */ 627 | // VALUE *dfp; /* share with method frame if it's only block */ 628 | // rb_iseq_t *iseq; 629 | // VALUE proc; 630 | 631 | if(block->iseq && !RUBY_VM_IFUNC_P(block->iseq)) { 632 | yg_cstring("iseq"); 633 | yajl_gen_map_open(ctx->yajl); 634 | //FIXME: id may be different (due to RBasic fields)!!! 635 | ygh_id("id", (VALUE)block->iseq); 636 | dump_iseq(block->iseq, ctx); 637 | yajl_gen_map_close(ctx->yajl); 638 | } else { 639 | ygh_id("iseq", (VALUE)block->iseq); 640 | } 641 | 642 | ygh_id("self", block->self); 643 | 644 | #ifdef HAVE_RB_BLOCK_T_LFP 645 | //FIXME: these are pointers to some memory, may be dumped more clever 646 | ygh_id("lfp", (VALUE)block->lfp); 647 | ygh_id("dfp", (VALUE)block->dfp); 648 | //lfp = local frame pointer? local_num elems? 649 | // dfp = ? 650 | #endif 651 | #ifdef HAVE_RB_BLOCK_T_KLASS 652 | ygh_id("class", (VALUE)block->klass); 653 | //TODO: VALUE*ep = ? 654 | #endif 655 | } 656 | 657 | 658 | 659 | static void yg_fiber_status(enum fiber_status status, walk_ctx_t* ctx){ 660 | switch(status){ 661 | case CREATED: yg_cstring("CREATED"); break; 662 | case RUNNING: yg_cstring("RUNNING"); break; 663 | case TERMINATED: yg_cstring("TERMINATED"); break; 664 | } 665 | } 666 | 667 | static void yg_fiber_type(enum context_type status, walk_ctx_t* ctx){ 668 | switch(status){ 669 | case CONTINUATION_CONTEXT: yg_cstring("CONTINUATION_CONTEXT"); break; 670 | case FIBER_CONTEXT: yg_cstring("FIBER_CONTEXT"); break; 671 | case ROOT_FIBER_CONTEXT: yg_cstring("ROOT_FIBER_CONTEXT"); break; 672 | } 673 | } 674 | 675 | static void dump_locations(VALUE* p, long int n, walk_ctx_t *ctx){ 676 | if(n > 0){ 677 | VALUE* x = p; 678 | while(n--){ 679 | VALUE v = *x; 680 | if(is_in_heap((void*)v, NULL)) //TODO: sometimes thread is known, may get its th->vm->objspace (in case there's a few) 681 | yg_id(v); 682 | x++; 683 | } 684 | } 685 | } 686 | 687 | static void dump_thread(const rb_thread_t* th, walk_ctx_t *ctx); 688 | 689 | 690 | static int vm_dump_each_thread_func(st_data_t key, VALUE obj, walk_ctx_t *ctx){ 691 | //here stored 'self' from thread 692 | // yg_map(); 693 | ygh_id("id", obj); 694 | // const rb_thread_t *th = obj; //RTYPEDDATA_DATA(obj); 695 | 696 | //or direct pointer? 697 | // dump_thread(th, ctx); 698 | //yg_id(obj); 699 | // yg_map_end(); 700 | return ST_CONTINUE; 701 | } 702 | 703 | 704 | static void dump_data_if_known(VALUE obj, walk_ctx_t *ctx){ 705 | 706 | // VM 707 | // VM/env 708 | // VM/thread 709 | // autoload 710 | // binding <- 711 | // encoding 712 | // iseq <- 713 | // method <- 714 | // mutex 715 | // proc <- 716 | // thgroup 717 | // time 718 | // barrier 719 | // strio 720 | // etc... 721 | 722 | const char* typename = RTYPEDDATA_TYPE(obj)->wrap_struct_name; 723 | 724 | if(!strcmp("iseq", typename)){ 725 | const rb_iseq_t* iseq = RTYPEDDATA_DATA(obj); 726 | dump_iseq(iseq, ctx); 727 | return; 728 | } 729 | 730 | if(!strcmp("autoload", typename)){ 731 | const st_table *tbl = RTYPEDDATA_DATA(obj); 732 | yg_cstring("val"); 733 | yajl_gen_map_open(ctx->yajl); 734 | st_foreach((st_table *)tbl, dump_method_entry_i, (st_data_t)ctx); //removing const, but this should not affect hash 735 | yajl_gen_map_close(ctx->yajl); 736 | return; 737 | } 738 | 739 | if(!strcmp("barrier", typename)){ 740 | ygh_id("val", (VALUE)RTYPEDDATA_DATA(obj)); 741 | return; 742 | } 743 | 744 | if(!strcmp("proc", typename)){ 745 | const rb_proc_t *proc = RTYPEDDATA_DATA(obj); 746 | ygh_int("is_lambda", proc->is_lambda); 747 | ygh_id("blockprocval", proc->blockprocval); 748 | ygh_id("envval", proc->envval); 749 | //TODO: dump refs from env here (they're dumped in env itself, but just to make analysis easier)? 750 | 751 | //TODO: is this proc->block.iseq sometimes bound somewhere (seems to be not, but dupes exist) 752 | yg_cstring("block"); 753 | yajl_gen_map_open(ctx->yajl); 754 | dump_block(&proc->block, ctx); 755 | yajl_gen_map_close(ctx->yajl); 756 | return; 757 | } 758 | 759 | if(!strcmp("method", typename)){ 760 | struct METHOD *data = RTYPEDDATA_DATA(obj); 761 | ygh_id("rclass", data->rclass); 762 | ygh_id("recv", data->recv); 763 | ygh_int("method_id", data->id); 764 | 765 | yg_cstring("method"); 766 | if(METHOD_DEFINITIONP(data)){ 767 | dump_method_definition_as_value(METHOD_DEFINITIONP(data), ctx); 768 | } 769 | return; 770 | } 771 | 772 | if(!strcmp("binding", typename)){ 773 | rb_binding_t *bind = RTYPEDDATA_DATA(obj); 774 | if(!bind) return; 775 | ygh_id("env", bind->env); 776 | #ifdef HAVE_RB_BINDING_T_FILENAME 777 | ygh_id("filename", bind->filename); 778 | ygh_int("line", bind->line_no); 779 | #else 780 | ygh_id("filename", bind->path); 781 | ygh_int("line", bind->first_lineno); 782 | #endif 783 | return; 784 | } 785 | 786 | if(!strcmp("VM/env", typename)){ 787 | const rb_env_t* env = RTYPEDDATA_DATA(obj); 788 | yg_cstring("env"); 789 | yajl_gen_array_open(ctx->yajl); 790 | dump_locations(env->env, env->env_size, ctx); 791 | yajl_gen_array_close(ctx->yajl); 792 | 793 | ygh_int("local_size", env->local_size); 794 | ygh_id("prev_envval", env->prev_envval); 795 | 796 | yg_cstring("block"); 797 | yajl_gen_map_open(ctx->yajl); 798 | dump_block(&env->block, ctx); 799 | yajl_gen_map_close(ctx->yajl); 800 | return; 801 | } 802 | 803 | if(!strcmp("enumerator", typename)){ 804 | struct enumerator *ptr = RTYPEDDATA_DATA(obj); 805 | ygh_id("obj", ptr->obj); 806 | ygh_id("args", ptr->args); 807 | ygh_id("fib", ptr->fib); 808 | ygh_id("dst", ptr->dst); 809 | ygh_id("lookahead", ptr->lookahead); 810 | ygh_id("feedvalue", ptr->feedvalue); 811 | ygh_id("stop_exc", ptr->stop_exc); 812 | return; 813 | } 814 | 815 | if(!strcmp("generator", typename)){ 816 | struct generator *ptr = RTYPEDDATA_DATA(obj); 817 | ygh_id("proc", ptr->proc); 818 | return; 819 | } 820 | 821 | if(!strcmp("yielder", typename)){ 822 | struct yielder *ptr = RTYPEDDATA_DATA(obj); 823 | ygh_id("proc", ptr->proc); 824 | return; 825 | } 826 | 827 | if(!strcmp("VM", typename)){ 828 | const rb_vm_t *vm = RTYPEDDATA_DATA(obj); 829 | 830 | ygh_id("thgroup_default", vm->thgroup_default); 831 | // rb_gc_register_mark_object - goes in that array (not to be freed until vm dies) 832 | ygh_id("mark_object_ary", vm->mark_object_ary); 833 | ygh_id("load_path", vm->load_path); 834 | ygh_id("loaded_features", vm->loaded_features); 835 | ygh_id("top_self", vm->top_self); 836 | ygh_id("coverages", vm->coverages); 837 | 838 | //TODO: 839 | if (vm->living_threads) { 840 | yg_cstring("threads"); 841 | yg_array(); 842 | st_foreach(vm->living_threads, vm_dump_each_thread_func, (st_data_t)ctx); 843 | yg_array_end(); 844 | } 845 | // rb_gc_mark_locations(vm->special_exceptions, vm->special_exceptions + ruby_special_error_count); 846 | 847 | // if (vm->loading_table) { 848 | // rb_mark_tbl(vm->loading_table); 849 | // } 850 | 851 | // mark_event_hooks(vm->event_hooks); 852 | 853 | // for (i = 0; i < RUBY_NSIG; i++) { 854 | // if (vm->trap_list[i].cmd) 855 | // rb_gc_mark(vm->trap_list[i].cmd); 856 | // } 857 | // } 858 | return; 859 | } 860 | 861 | if(!strcmp("fiber", typename)){ 862 | rb_fiber_t *fib = RTYPEDDATA_DATA(obj); 863 | ygh_id("prev", fib->prev); 864 | yg_cstring("status"); 865 | yg_fiber_status(fib->status, ctx); 866 | 867 | yg_cstring("cont"); 868 | yg_map(); 869 | // actually this is embedded continuation object, these may be standalone datas in the wild 870 | yg_cstring("type"); 871 | yg_fiber_type(fib->cont.type, ctx); 872 | 873 | ygh_id("self", fib->cont.self); 874 | ygh_id("value", fib->cont.value); 875 | 876 | yg_cstring("saved_thread"); 877 | yg_map(); 878 | dump_thread(&fib->cont.saved_thread, ctx); 879 | yg_map_end(); 880 | 881 | //stacks: 882 | if(fib->cont.vm_stack) { 883 | yg_cstring("vm_stack"); 884 | yg_array(); 885 | dump_locations(fib->cont.vm_stack, fib->cont.vm_stack_slen + fib->cont.vm_stack_clen, ctx); 886 | yg_array_end(); 887 | } 888 | if (fib->cont.machine_stack) { 889 | yg_cstring("mach_stack"); 890 | yg_array(); 891 | dump_locations(fib->cont.machine_stack, fib->cont.machine_stack_size, ctx); 892 | yg_array_end(); 893 | } 894 | yg_map_end(); 895 | return; 896 | } 897 | 898 | if(!strcmp("VM/thread", typename)){ 899 | const rb_thread_t *th = RTYPEDDATA_DATA(obj); 900 | dump_thread(th, ctx); 901 | return; 902 | } 903 | 904 | if(!strcmp("time", typename)){ 905 | // struct time_object *tobj = RTYPEDDATA_DATA(obj); 906 | // if (!tobj) return; 907 | // if (!FIXWV_P(tobj->timew)) 908 | // rb_gc_mark(w2v(tobj->timew)); 909 | // rb_gc_mark(tobj->vtm.year); 910 | // rb_gc_mark(tobj->vtm.subsecx); 911 | // rb_gc_mark(tobj->vtm.utc_offset); 912 | VALUE flt = rb_funcall(obj, rb_intern("to_f"), 0); 913 | if(TYPE(flt) == T_FLOAT){ ygh_double("val", NUM2DBL(flt)); } 914 | return; 915 | } 916 | 917 | if(!strcmp("thgroup", typename)){ 918 | const struct thgroup* gr = RTYPEDDATA_DATA(obj); 919 | ygh_id("group", gr->group); 920 | ygh_int("enclosed", gr->enclosed); 921 | return; 922 | } 923 | 924 | 925 | } 926 | 927 | static VALUE rb_class_real_checked(VALUE cl) 928 | { 929 | if (cl == 0 || IMMEDIATE_P(cl)) 930 | return 0; 931 | while (cl && ((RBASIC(cl)->flags & FL_SINGLETON) || BUILTIN_TYPE(cl) == T_ICLASS)) { 932 | if(RCLASS_EXT(cl) && RCLASS_SUPER(cl)){ 933 | cl = RCLASS_SUPER(cl); 934 | } else { 935 | return 0; 936 | } 937 | } 938 | return cl; 939 | } 940 | 941 | static inline void walk_live_object(VALUE obj, walk_ctx_t *ctx){ 942 | //note: BUILTIN_TYPE is only for heap, for embedded use TYPE 943 | const int bt_type = BUILTIN_TYPE(obj); 944 | ctx->live_objects++; 945 | yajl_gen_map_open(ctx->yajl); 946 | 947 | ygh_int("id", NUM2LONG(rb_obj_id(obj))); 948 | ygh_cstring("bt", rb_type_str(bt_type)); 949 | 950 | //TODO: 951 | #ifdef GC_DEBUG 952 | //RVALUE etc. has file/line info in this case 953 | #endif 954 | 955 | yg_cstring("class"); 956 | yg_id(rb_class_of(obj)); 957 | 958 | //ivars for !(obj|class|module): 959 | // if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) 960 | // return generic_ivar_get(obj, id, warn); 961 | 962 | // for generic types ivars are held separately in a table 963 | if(bt_type != T_OBJECT && bt_type != T_CLASS && bt_type != T_MODULE && bt_type != T_ICLASS){ 964 | st_table* generic_tbl = rb_generic_ivar_table(obj); 965 | if(generic_tbl){ 966 | yg_cstring("generic_ivars"); 967 | yg_map(); 968 | st_foreach(generic_tbl, dump_iv_entry, (st_data_t)ctx); 969 | yg_map_end(); 970 | } 971 | } 972 | 973 | switch(bt_type){ // no need to call TYPE(), as value is on heap 974 | case T_NODE: 975 | dump_node(RNODE(obj), ctx); 976 | break; 977 | case T_STRING: 978 | //TODO: limit string len! 979 | { 980 | int enc_i = rb_enc_get_index(obj); 981 | rb_encoding* enc = rb_enc_from_index(enc_i); 982 | if(enc){ 983 | ygh_cstring("encoding", enc->name); 984 | } 985 | //FIXME: convert encoding and dump? 986 | //if(enc_i == rb_usascii_encindex()) 987 | //this produces warnings on dump read, but recoverable 988 | ygh_string("val", RSTRING_PTR(obj), (unsigned int)RSTRING_LEN(obj)); 989 | } 990 | break; 991 | case T_SYMBOL: 992 | ygh_cstring("val", rb_id2name(SYM2ID(obj))); 993 | break; 994 | case T_REGEXP: 995 | { 996 | int enc_i = rb_enc_get_index(obj); 997 | rb_encoding* enc = rb_enc_from_index(enc_i); 998 | if(enc){ 999 | ygh_cstring("encoding", enc->name); 1000 | } 1001 | //FIXME: encodings? 1002 | // if(enc_i == rb_usascii_encindex()) 1003 | ygh_string("val", RREGEXP_SRC_PTR(obj), (unsigned int)RREGEXP_SRC_LEN(obj)); 1004 | } 1005 | break; 1006 | // T(T_MATCH); 1007 | 1008 | case T_ARRAY: 1009 | // if (FL_TEST(obj, ELTS_SHARED)) ... 1010 | yg_cstring("val"); 1011 | yajl_gen_array_open(ctx->yajl); 1012 | { 1013 | long i, len = RARRAY_LEN(obj); 1014 | VALUE *ptr = RARRAY_PTR(obj); 1015 | for(i = 0; i < len; i++) yg_id(*ptr++); 1016 | } 1017 | yajl_gen_array_close(ctx->yajl); 1018 | break; 1019 | 1020 | case T_STRUCT: 1021 | yg_cstring("refs"); //ivars 1022 | yajl_gen_array_open(ctx->yajl); 1023 | { 1024 | long len = RSTRUCT_LEN(obj); 1025 | VALUE *ptr = RSTRUCT_PTR(obj); 1026 | while (len--) yg_id(*ptr++); 1027 | } 1028 | yajl_gen_array_close(ctx->yajl); 1029 | break; 1030 | 1031 | case T_HASH: 1032 | dump_hash(obj, ctx); 1033 | break; 1034 | 1035 | case T_OBJECT: 1036 | //yg_cstring("class"); 1037 | //yg_id(rb_class_of(obj)); 1038 | 1039 | 1040 | // yg_cstring("refs"); //ivars 1041 | // yajl_gen_array_open(ctx->yajl); 1042 | // { 1043 | // long i, len = ROBJECT_NUMIV(obj); 1044 | // VALUE *ptr = ROBJECT_IVPTR(obj); 1045 | // for (i = 0; i < len; i++) yg_id(*ptr++); 1046 | // } 1047 | // yajl_gen_array_close(ctx->yajl); 1048 | yg_cstring("ivs"); 1049 | yajl_gen_map_open(ctx->yajl); //TODO: what are iv keys? 1050 | rb_ivar_foreach(obj, dump_iv_entry, (st_data_t)ctx); 1051 | yajl_gen_map_close(ctx->yajl); 1052 | break; 1053 | 1054 | case T_ICLASS: 1055 | case T_CLASS: 1056 | case T_MODULE: 1057 | { 1058 | VALUE name = rb_ivar_get(obj, classid); 1059 | if (name != Qnil){ 1060 | ygh_cstring("name", rb_id2name(SYM2ID(name))); 1061 | } else if(RCLASS_EXT(obj) && RCLASS_EXT(obj)->super){ 1062 | // more expensive + allocates a string 1063 | VALUE path = rb_class_path(rb_class_real_checked(obj)); 1064 | 1065 | ygh_rstring("name", path); 1066 | } 1067 | 1068 | yg_cstring("methods"); 1069 | yajl_gen_map_open(ctx->yajl); 1070 | 1071 | if(RCLASS_M_TBL(obj) && RCLASS_M_TBL(obj)->num_entries > 0){ // num check not necessary? 1072 | st_foreach(RCLASS_M_TBL(obj), dump_method_entry_i, (st_data_t)ctx); 1073 | } 1074 | yajl_gen_map_close(ctx->yajl); 1075 | 1076 | if (RCLASS_EXT(obj)){ 1077 | if(RCLASS_IV_TBL(obj) && RCLASS_IV_TBL(obj)->num_entries > 0){ 1078 | yg_cstring("ivs"); 1079 | yajl_gen_map_open(ctx->yajl); //TODO: what are iv keys? 1080 | st_foreach(RCLASS_IV_TBL(obj), dump_iv_entry, (st_data_t)ctx); 1081 | yajl_gen_map_close(ctx->yajl); 1082 | } 1083 | 1084 | #ifdef HAVE_CONSTANT_H 1085 | // this is for 1.9.3 or so - where rb_classext_t has const_tbl 1086 | if(RCLASS_CONST_TBL(obj)){ 1087 | yg_cstring("consts"); 1088 | yg_map(); 1089 | st_foreach(RCLASS_CONST_TBL(obj), dump_const_entry_i, (st_data_t)ctx); 1090 | yg_map_end(); 1091 | } 1092 | #endif 1093 | 1094 | ygh_id("super", RCLASS_SUPER(obj)); 1095 | } 1096 | } 1097 | break; 1098 | 1099 | case T_FILE: 1100 | yg_cstring("refs"); //ivars 1101 | yajl_gen_array_open(ctx->yajl); 1102 | if (RFILE(obj)->fptr) { 1103 | yg_id(RFILE(obj)->fptr->pathv); 1104 | yg_id(RFILE(obj)->fptr->tied_io_for_writing); 1105 | yg_id(RFILE(obj)->fptr->writeconv_asciicompat); 1106 | yg_id(RFILE(obj)->fptr->writeconv_pre_ecopts); 1107 | yg_id(RFILE(obj)->fptr->encs.ecopts); 1108 | yg_id(RFILE(obj)->fptr->write_lock); 1109 | } 1110 | yajl_gen_array_close(ctx->yajl); 1111 | break; 1112 | 1113 | case T_FIXNUM: 1114 | ygh_int("val", FIX2LONG(obj)); 1115 | break; 1116 | case T_FLOAT: 1117 | ygh_double("val", RFLOAT_VALUE(obj)); 1118 | break; 1119 | case T_RATIONAL: 1120 | //TODO: dump value for immediate components 1121 | yg_cstring("refs"); 1122 | yajl_gen_array_open(ctx->yajl); 1123 | yg_id(RRATIONAL(obj)->num); 1124 | yg_id(RRATIONAL(obj)->den); 1125 | yajl_gen_array_close(ctx->yajl); 1126 | break; 1127 | case T_COMPLEX: 1128 | yg_cstring("refs"); 1129 | yajl_gen_array_open(ctx->yajl); 1130 | yg_id(RCOMPLEX(obj)->real); 1131 | yg_id(RCOMPLEX(obj)->imag); 1132 | yajl_gen_array_close(ctx->yajl); 1133 | break; 1134 | 1135 | case T_BIGNUM: 1136 | { 1137 | long len = RBIGNUM_LEN(obj), i; 1138 | BDIGIT* digits = RBIGNUM_DIGITS(obj); 1139 | yg_cstring("digits"); 1140 | yajl_gen_array_open(ctx->yajl); 1141 | for(i = 0; i < len; i++) 1142 | yg_int(digits[i]); 1143 | yajl_gen_array_close(ctx->yajl); 1144 | } 1145 | break; 1146 | 1147 | case T_DATA: // data of extensions + raw bytecode etc., refs undumpable? maybe in some way mess with mark callback? (need to intercept rb_gc_mark :( ) 1148 | if(RTYPEDDATA_P(obj)){ 1149 | ygh_cstring("type_name", RTYPEDDATA_TYPE(obj)->wrap_struct_name); 1150 | #if HAVE_RB_DATA_TYPE_T_FUNCTION 1151 | if(RTYPEDDATA_TYPE(obj)->function.dsize) ygh_int("size", RTYPEDDATA_TYPE(obj)->function.dsize(RTYPEDDATA_DATA(obj))); 1152 | #else 1153 | if(RTYPEDDATA_TYPE(obj)->dsize) ygh_int("size", RTYPEDDATA_TYPE(obj)->dsize(RTYPEDDATA_DATA(obj))); 1154 | #endif 1155 | dump_data_if_known(obj, ctx); 1156 | } 1157 | break; 1158 | 1159 | // T(T_UNDEF); 1160 | default: break; 1161 | } 1162 | yajl_gen_map_close(ctx->yajl); 1163 | flush_yajl(ctx); 1164 | fprintf(ctx->file, "\n"); 1165 | } 1166 | 1167 | /* 1168 | * will be called several times (the number of heap slot, at current implementation) with: 1169 | * vstart: a pointer to the first living object of the heap_slot. 1170 | * vend: a pointer to next to the valid heap_slot area. 1171 | * stride: a distance to next VALUE. 1172 | */ 1173 | static int objspace_walker(void *vstart, void *vend, size_t stride, void* data) { 1174 | VALUE v = (VALUE)vstart; 1175 | walk_ctx_t *ctx = data; 1176 | ctx->walker_called++; 1177 | 1178 | for (; v != (VALUE)vend; v += stride) { 1179 | if (RBASIC(v)->flags) { // is live object 1180 | walk_live_object(v, ctx); 1181 | } 1182 | } 1183 | // return 1; //stop 1184 | return 0; // continue to iteration 1185 | } 1186 | 1187 | 1188 | //TODO: move to separate header? 1189 | #ifndef SET_MACHINE_STACK_END 1190 | NOINLINE(static void rb_gc_set_stack_end(VALUE **stack_end_p)); 1191 | #define SET_MACHINE_STACK_END(p) rb_gc_set_stack_end(p) 1192 | #define USE_CONSERVATIVE_STACK_END 1193 | // #endif 1194 | static void 1195 | rb_gc_set_stack_end(VALUE **stack_end_p) 1196 | { 1197 | VALUE stack_end; 1198 | *stack_end_p = &stack_end; 1199 | } 1200 | #endif 1201 | 1202 | #ifdef __ia64 1203 | #define SET_STACK_END (SET_MACHINE_STACK_END(&th->machine_stack_end), th->machine_register_stack_end = rb_ia64_bsp()) 1204 | #else 1205 | #define SET_STACK_END SET_MACHINE_STACK_END(&th->machine_stack_end) 1206 | #endif 1207 | 1208 | #define STACK_START (th->machine_stack_start) 1209 | #define STACK_END (th->machine_stack_end) 1210 | #define STACK_LEVEL_MAX (th->machine_stack_maxsize/sizeof(VALUE)) 1211 | 1212 | #if STACK_GROW_DIRECTION < 0 1213 | # define STACK_LENGTH (size_t)(STACK_START - STACK_END) 1214 | #elif STACK_GROW_DIRECTION > 0 1215 | # define STACK_LENGTH (size_t)(STACK_END - STACK_START + 1) 1216 | #else 1217 | # define STACK_LENGTH ((STACK_END < STACK_START) ? (size_t)(STACK_START - STACK_END) \ 1218 | : (size_t)(STACK_END - STACK_START + 1)) 1219 | #endif 1220 | #if !STACK_GROW_DIRECTION 1221 | int ruby_stack_grow_direction; 1222 | int 1223 | ruby_get_stack_grow_direction(volatile VALUE *addr) 1224 | { 1225 | VALUE *end; 1226 | SET_MACHINE_STACK_END(&end); 1227 | 1228 | if (end > addr) return ruby_stack_grow_direction = 1; 1229 | return ruby_stack_grow_direction = -1; 1230 | } 1231 | #endif 1232 | 1233 | #if STACK_GROW_DIRECTION < 0 1234 | #define GET_STACK_BOUNDS(start, end, appendix) ((start) = STACK_END, (end) = STACK_START) 1235 | #elif STACK_GROW_DIRECTION > 0 1236 | #define GET_STACK_BOUNDS(start, end, appendix) ((start) = STACK_START, (end) = STACK_END+(appendix)) 1237 | #else 1238 | #define GET_STACK_BOUNDS(start, end, appendix) \ 1239 | ((STACK_END < STACK_START) ? \ 1240 | ((start) = STACK_END, (end) = STACK_START) : ((start) = STACK_START, (end) = STACK_END+(appendix))) 1241 | #endif 1242 | 1243 | #define rb_setjmp(env) RUBY_SETJMP(env) 1244 | #define rb_jmp_buf rb_jmpbuf_t 1245 | 1246 | #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) 1247 | 1248 | ///////////// 1249 | 1250 | 1251 | static inline int is_in_heap(void *ptr, void* osp){ 1252 | rb_objspace_t *objspace = osp; 1253 | if(!ptr) return false; 1254 | if(!objspace) objspace = GET_THREAD()->vm->objspace; 1255 | return is_pointer_to_heap(objspace, ptr); 1256 | } 1257 | 1258 | 1259 | 1260 | static int 1261 | dump_backtrace(void* data, VALUE file, int line, VALUE method, int argc, VALUE* argv) 1262 | { 1263 | walk_ctx_t *ctx = data; 1264 | const char *filename = NIL_P(file) ? "" : RSTRING_PTR(file); 1265 | int i; 1266 | 1267 | yg_map(); 1268 | 1269 | ygh_cstring("file", filename); 1270 | ygh_int("line", line); 1271 | 1272 | if (NIL_P(method)) { 1273 | //fprintf(fp, "\tfrom %s:%d:in unknown method\n", filename, line); 1274 | } 1275 | else { 1276 | //fprintf(fp, "\tfrom %s:%d:in `%s'\n", filename, line, RSTRING_PTR(method)); 1277 | ygh_rstring("method", method); 1278 | } 1279 | ygh_int("argc", argc); 1280 | if(argc > 0){ 1281 | yg_cstring("argv"); 1282 | yg_array(); 1283 | for(i = 0; i < argc; i++) 1284 | yg_id(argv[i]); 1285 | yg_array_end(); 1286 | } 1287 | yg_map_end(); 1288 | return FALSE; 1289 | } 1290 | 1291 | typedef int (rb_backtrace_iter_ext_func)(void *arg, VALUE file, int line, VALUE method_name, int argc, VALUE* argv); 1292 | 1293 | // copied from ruby_ext_backtrace 1294 | static int 1295 | vm_backtrace_each_ext(const rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_ext_func *iter, void *arg) 1296 | { 1297 | const rb_control_frame_t *limit_cfp = th->cfp; 1298 | const rb_control_frame_t *cfp = (void *)(th->stack + th->stack_size); 1299 | VALUE file = Qnil; 1300 | VALUE* argv; 1301 | int line_no = 0; 1302 | 1303 | cfp -= 2; 1304 | //skip lev frames: 1305 | while (lev-- >= 0) { 1306 | if (++limit_cfp > cfp) 1307 | return FALSE; 1308 | } 1309 | 1310 | if (init) (*init)(arg); 1311 | 1312 | limit_cfp = RUBY_VM_NEXT_CONTROL_FRAME(limit_cfp); 1313 | if (th->vm->progname) file = th->vm->progname; 1314 | 1315 | while (cfp > limit_cfp) { 1316 | #ifdef HAVE_RB_CONTROL_FRAME_T_BP 1317 | VALUE* bp = cfp->bp; 1318 | #else 1319 | VALUE* bp = cfp->sp; //?? 1320 | #endif 1321 | if (cfp->iseq != 0) { 1322 | if (cfp->pc != 0) { 1323 | rb_iseq_t *iseq = cfp->iseq; 1324 | 1325 | line_no = rb_vm_get_sourceline(cfp); 1326 | #ifdef HAVE_RB_ISEQ_T_FILENAME 1327 | file = iseq->filename; 1328 | #else 1329 | file = iseq->location.path; 1330 | #endif 1331 | //arguments pushed this way: *reg_cfp->sp++ = recv; for (i = 0; i < argc; i++) *reg_cfp->sp++ = argv[i]; 1332 | //local vars = cfp->iseq->local_size - cfp->iseq->arg_size; 1333 | //in memory: receiver params locals (bp(incremented)) 1334 | argv = &bp[- cfp->iseq->local_size - 1]; 1335 | if ((*iter)(arg, file, line_no, 1336 | #ifdef HAVE_RB_ISEQ_T_LOCATION 1337 | iseq->location.label 1338 | #else 1339 | iseq->name 1340 | #endif 1341 | , cfp->iseq->arg_size, argv)) break; 1342 | } 1343 | } else 1344 | if (RUBYVM_CFUNC_FRAME_P(cfp)) { 1345 | ID id = cfp->me->def? cfp->me->def->original_id : cfp->me->called_id; 1346 | 1347 | if (NIL_P(file)) file = ruby_engine_name; 1348 | 1349 | if (id != ID_ALLOCATOR){ 1350 | argv = NULL; 1351 | // when argc==-1/-2(variable length params without/with splat) - the cfp has no info on params count :( 1352 | //TODO: infere from somewhere ex. find self in stack? (not guaranted btw, for example: obj.method(obj, 123, obj) - will find last param instead of self) 1353 | if(cfp->me->def->body.cfunc.argc >= 0){ //only fixed args 1354 | argv = &bp[- cfp->me->def->body.cfunc.argc - 2]; // args+self, bp was incremented thus minus 2 1355 | } 1356 | //file+line no from previous iseq frame 1357 | if((*iter)(arg, file, line_no, rb_id2str(id), cfp->me->def->body.cfunc.argc, argv)) break; 1358 | } 1359 | } 1360 | cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp); 1361 | } 1362 | return TRUE; 1363 | } 1364 | 1365 | static void dump_thread(const rb_thread_t* th, walk_ctx_t *ctx){ 1366 | rb_iseq_t *iseq; 1367 | int line_no; 1368 | ID id; 1369 | #ifdef HAVE_RB_EVENT_HOOK_T_DATA 1370 | rb_event_hook_t *hook = th->event_hooks; 1371 | #else 1372 | struct rb_event_hook_struct *hook = th->event_hooks.hooks; 1373 | #endif 1374 | 1375 | if(th->stack){ 1376 | VALUE *p = th->stack; 1377 | VALUE *sp = th->cfp->sp; 1378 | rb_control_frame_t *cfp = th->cfp; 1379 | rb_control_frame_t *limit_cfp = (void *)(th->stack + th->stack_size); 1380 | 1381 | yg_cstring("stack"); 1382 | yajl_gen_array_open(ctx->yajl); 1383 | while (p < sp) yg_id(*p++); 1384 | yajl_gen_array_close(ctx->yajl); 1385 | yg_cstring("stack_locations"); 1386 | yg_array(); 1387 | dump_locations(p, th->mark_stack_len, ctx); 1388 | yg_array_end(); 1389 | 1390 | yg_cstring("cfp"); 1391 | yajl_gen_array_open(ctx->yajl); 1392 | //TODO: this is kind of backtrace, but other direction plus some other info, merge it in backtrace. 1393 | while (cfp != limit_cfp) { 1394 | yajl_gen_map_open(ctx->yajl); 1395 | iseq = cfp->iseq; 1396 | ygh_id("proc", cfp->proc); 1397 | ygh_id("self", cfp->self); 1398 | if (iseq) { 1399 | ygh_id("iseq", RUBY_VM_NORMAL_ISEQ_P(iseq) ? iseq->self : (VALUE)iseq); 1400 | line_no = rb_vm_get_sourceline(cfp); 1401 | //TODO: dry? 1402 | #ifdef HAVE_RB_ISEQ_T_FILENAME 1403 | ygh_rstring("file", iseq->filename); 1404 | #else 1405 | ygh_rstring("file", iseq->location.path); 1406 | #endif 1407 | ygh_int("line_no",line_no); 1408 | } 1409 | if (cfp->me){ 1410 | const rb_method_entry_t *me = cfp->me; 1411 | //((rb_method_entry_t *)cfp->me)->mark = 1; 1412 | yg_cstring("me"); 1413 | yajl_gen_map_open(ctx->yajl); 1414 | // 1415 | //rb_method_flag_t flag; 1416 | // char mark; 1417 | //rb_method_definition_t *def; 1418 | ygh_id("klass", me->klass); 1419 | id = me->called_id; 1420 | 1421 | if(me->def){ 1422 | id = me->def->original_id; 1423 | yg_cstring("def"); 1424 | dump_method_definition_as_value(me->def, ctx); 1425 | } 1426 | if(id != ID_ALLOCATOR) 1427 | ygh_rstring("meth_id", rb_id2str(id)); 1428 | yajl_gen_map_close(ctx->yajl); 1429 | } 1430 | cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); 1431 | yajl_gen_map_close(ctx->yajl); 1432 | } 1433 | yajl_gen_array_close(ctx->yajl); 1434 | } 1435 | 1436 | yg_cstring("backtrace"); 1437 | yg_array(); 1438 | vm_backtrace_each_ext(th, -1, NULL, dump_backtrace, ctx); 1439 | yg_array_end(); 1440 | 1441 | //TODO: mark other... 1442 | ygh_id("first_proc", th->first_proc); 1443 | if (th->first_proc) ygh_id("first_proc", th->first_args); 1444 | 1445 | ygh_id("thgroup", th->thgroup); 1446 | ygh_id("value", th->value); 1447 | ygh_id("errinfo", th->errinfo); 1448 | 1449 | #ifdef HAVE_RB_THREAD_T_THROWN_ERRINFO 1450 | ygh_id("thrown_errinfo", th->thrown_errinfo); 1451 | ygh_id("local_svar", th->local_svar); 1452 | #endif 1453 | 1454 | ygh_id("top_self", th->top_self); 1455 | ygh_id("top_wrapper", th->top_wrapper); 1456 | ygh_id("fiber", th->fiber); 1457 | ygh_id("root_fiber", th->root_fiber); 1458 | ygh_id("stat_insn_usage", th->stat_insn_usage); 1459 | ygh_id("last_status", th->last_status); 1460 | ygh_id("locking_mutex", th->locking_mutex); 1461 | 1462 | if (GET_THREAD() != th && th->machine_stack_start && th->machine_stack_end) { 1463 | // rb_gc_mark_machine_stack(th); 1464 | VALUE *stack_start, *stack_end; 1465 | GET_STACK_BOUNDS(stack_start, stack_end, 0); 1466 | // /sizeof(VALUE)? 1467 | yg_cstring("mach_stack"); 1468 | yg_array(); 1469 | dump_locations(stack_start, (stack_end-stack_start), ctx); 1470 | yg_array_end(); 1471 | 1472 | yg_cstring("mach_regs"); 1473 | yg_array(); 1474 | dump_locations((VALUE *)&th->machine_regs, sizeof(th->machine_regs) / sizeof(VALUE), ctx); 1475 | yg_array_end(); 1476 | } 1477 | 1478 | yg_cstring("local_storage"); 1479 | yajl_gen_map_open(ctx->yajl); 1480 | if(th->local_storage){ 1481 | st_foreach(th->local_storage, dump_iv_entry, (st_data_t)ctx); //? 1482 | } 1483 | yajl_gen_map_close(ctx->yajl); 1484 | 1485 | yg_cstring("event_hooks"); 1486 | yajl_gen_array_open(ctx->yajl); 1487 | while(hook){ 1488 | yg_id(hook->data); 1489 | hook = hook->next; 1490 | } 1491 | yajl_gen_array_close(ctx->yajl); 1492 | } 1493 | 1494 | 1495 | static void dump_machine_context(walk_ctx_t *ctx){ 1496 | VALUE* x; 1497 | unsigned long n; 1498 | 1499 | //TODO: other threads? 1500 | rb_thread_t* th = GET_THREAD()->vm->main_thread; //GET_THREAD(); 1501 | union { 1502 | rb_jmp_buf j; 1503 | VALUE v[sizeof(rb_jmp_buf) / sizeof(VALUE)]; 1504 | } save_regs_gc_mark; 1505 | VALUE *stack_start, *stack_end; 1506 | 1507 | 1508 | FLUSH_REGISTER_WINDOWS; 1509 | /* This assumes that all registers are saved into the jmp_buf (and stack) */ 1510 | rb_setjmp(save_regs_gc_mark.j); 1511 | 1512 | SET_STACK_END; 1513 | GET_STACK_BOUNDS(stack_start, stack_end, 1); 1514 | 1515 | yg_cstring("registers"); 1516 | yajl_gen_array_open(ctx->yajl); 1517 | //mark_locations_array(objspace, save_regs_gc_mark.v, numberof(save_regs_gc_mark.v)); 1518 | x = save_regs_gc_mark.v; 1519 | n = numberof(save_regs_gc_mark.v); 1520 | while (n--) { 1521 | VALUE v = *(x++); 1522 | if(is_in_heap((void*)v, NULL)) 1523 | yg_id(v); 1524 | } 1525 | yajl_gen_array_close(ctx->yajl); 1526 | 1527 | yg_cstring("stack"); 1528 | yajl_gen_array_open(ctx->yajl); 1529 | //rb_gc_mark_locations(stack_start, stack_end); 1530 | if(stack_start < stack_end){ 1531 | n = stack_end - stack_start; 1532 | x = stack_start; 1533 | while (n--) { 1534 | VALUE v = *(x++); 1535 | //FIXME: other objspace (not default one?) 1536 | if(is_in_heap((void*)v, NULL)) { 1537 | yg_id(v); 1538 | } 1539 | } 1540 | } 1541 | 1542 | yajl_gen_array_close(ctx->yajl); 1543 | } 1544 | 1545 | #ifdef HAVE_RB_CLASS_TBL 1546 | // 1.9.2, rb_class_tbl fails to be linked in 1.9.3 :( 1547 | 1548 | static int dump_class_tbl_entry(ID key, rb_const_entry_t* ce/*st_data_t val*/, walk_ctx_t *ctx){ 1549 | const char* id; 1550 | if (!rb_is_const_id(key)) return ST_CONTINUE; //? 1551 | 1552 | if((id = rb_id2name(key))) 1553 | yg_cstring(id); 1554 | else 1555 | yg_cstring("(unknown)"); 1556 | yg_id(ce->value); 1557 | return ST_CONTINUE; 1558 | } 1559 | #endif 1560 | 1561 | #ifdef HAVE_RB_GLOBAL_TBL 1562 | static int dump_global_tbl_entry(ID key, struct rb_global_entry* ge/*st_data_t val*/, walk_ctx_t *ctx){ 1563 | const char* id = rb_id2name(key); 1564 | if(id) 1565 | yg_cstring(id); 1566 | else 1567 | yg_cstring("(unknown)"); 1568 | 1569 | yg_map(); 1570 | 1571 | Dl_info info; 1572 | if(dladdr(ge->var->getter, &info) && info.dli_sname){ 1573 | yg_cstring("getter"); 1574 | yg_cstring(info.dli_sname); 1575 | 1576 | if(!strcmp("rb_gvar_val_getter", info.dli_sname)){ 1577 | yg_cstring("data"); 1578 | yg_id((VALUE)ge->var->data); 1579 | } 1580 | } 1581 | 1582 | yg_cstring("setter"); 1583 | yg_funcaddr(ge->var->setter); 1584 | 1585 | yg_map_end(); 1586 | return ST_CONTINUE; 1587 | } 1588 | #endif 1589 | 1590 | 1591 | #include 1592 | 1593 | static int g_verbose = false; 1594 | static int log_printf(const char* format, ...){ 1595 | int res = 0; 1596 | va_list list; 1597 | va_start(list, format); 1598 | if(g_verbose) 1599 | res = vprintf(format, list); 1600 | va_end(list); 1601 | return res; 1602 | } 1603 | 1604 | #define log log_printf 1605 | 1606 | static VALUE heapdump_verbose(VALUE self){ 1607 | return g_verbose ? Qtrue : Qfalse; 1608 | } 1609 | 1610 | static VALUE heapdump_verbose_setter(VALUE self, VALUE verbose){ 1611 | g_verbose = RTEST(verbose); 1612 | return heapdump_verbose(self); 1613 | } 1614 | 1615 | 1616 | //public symbol, can be used from GDB 1617 | void heapdump_dump(const char* filename){ 1618 | struct walk_ctx ctx_o, *ctx = &ctx_o; 1619 | struct gc_list *list; 1620 | #ifdef HAVE_RB_CLASS_TBL 1621 | st_table *rb_class_tbl; 1622 | #endif 1623 | #ifdef HAVE_RB_GLOBAL_TBL 1624 | st_table *rb_global_tbl; 1625 | #endif 1626 | 1627 | memset(ctx, 0, sizeof(*ctx)); 1628 | 1629 | if(!filename){ 1630 | filename = "dump.json"; 1631 | } 1632 | log("Dump should go to %s\n", filename); 1633 | ctx->file = fopen(filename, "wt"); 1634 | ctx->yajl = yajl_gen_alloc(NULL,NULL); 1635 | yajl_gen_array_open(ctx->yajl); 1636 | 1637 | //dump origins: 1638 | yajl_gen_map_open(ctx->yajl); 1639 | ygh_cstring("id", "_ROOTS_"); 1640 | 1641 | log("machine context\n"); 1642 | 1643 | dump_machine_context(ctx); 1644 | flush_yajl(ctx); 1645 | // fprintf(ctx->file, "\n"); 1646 | 1647 | /* mark protected global variables */ 1648 | log("global_list\n"); 1649 | yg_cstring("globals"); 1650 | yg_array(); 1651 | for (list = GET_THREAD()->vm->global_List; list; list = list->next) { 1652 | VALUE v = *list->varptr; 1653 | yg_id(v); 1654 | } 1655 | yg_array_end(); 1656 | 1657 | //TODO: rb_global_tbl 1658 | #ifdef HAVE_RB_GLOBAL_TBL 1659 | rb_global_tbl = rb_get_global_tbl(); 1660 | if (rb_global_tbl && rb_global_tbl->num_entries > 0){ 1661 | log("globals\n"); 1662 | yg_cstring("global_tbl"); 1663 | yg_map(); 1664 | st_foreach(rb_global_tbl, dump_global_tbl_entry, (st_data_t)ctx); 1665 | yg_map_end(); 1666 | flush_yajl(ctx); 1667 | } 1668 | #endif 1669 | 1670 | #ifdef HAVE_RB_CLASS_TBL 1671 | rb_class_tbl = rb_get_class_tbl(); 1672 | if (rb_class_tbl && rb_class_tbl->num_entries > 0){ 1673 | log("classes\n"); 1674 | yg_cstring("classes"); 1675 | yg_map(); 1676 | st_foreach(rb_class_tbl, dump_class_tbl_entry, (st_data_t)ctx); 1677 | yg_map_end(); 1678 | flush_yajl(ctx); 1679 | } 1680 | #endif 1681 | 1682 | //TODO: other gc entry points - symbols, encodings, etc. 1683 | 1684 | yajl_gen_map_close(ctx->yajl); //id:roots 1685 | flush_yajl(ctx); 1686 | fprintf(ctx->file, "\n"); 1687 | 1688 | //now dump all live objects 1689 | log("starting objspace walk\n"); 1690 | rb_objspace_each_objects(objspace_walker, ctx); 1691 | 1692 | yajl_gen_array_close(ctx->yajl); 1693 | flush_yajl(ctx); 1694 | yajl_gen_free(ctx->yajl); 1695 | fclose(ctx->file); 1696 | 1697 | log("Walker called %d times, seen %d live objects.\n", ctx->walker_called, ctx->live_objects); 1698 | } 1699 | 1700 | static VALUE 1701 | rb_heapdump_dump(VALUE self, VALUE filename) 1702 | { 1703 | Check_Type(filename, T_STRING); 1704 | heapdump_dump(RSTRING_PTR(filename)); 1705 | return Qnil; 1706 | } 1707 | 1708 | 1709 | 1710 | // HeapDump.count_objects: 1711 | 1712 | #undef YAJL 1713 | #define YAJL yajl 1714 | static int 1715 | iterate_user_type_counts(VALUE key, VALUE value, yajl_gen yajl){ 1716 | yg_rstring(key); 1717 | yg_int(FIX2LONG(value)); 1718 | return ST_CONTINUE; 1719 | } 1720 | 1721 | static VALUE 1722 | heapdump_count_objects_core(yajl_gen yajl, VALUE string_prefixes, int do_gc){ 1723 | VALUE cls, class_name, prefix; 1724 | size_t counts[T_MASK+1]; 1725 | size_t freed = 0; 1726 | size_t total = 0; 1727 | size_t i; 1728 | long int n; 1729 | VALUE hash = rb_hash_new(); 1730 | rb_objspace_t *objspace = GET_THREAD()->vm->objspace; 1731 | 1732 | yg_map(); 1733 | if(do_gc){ 1734 | yg_cstring("gc_ran"); 1735 | yg_bool(true); 1736 | rb_gc_start(); 1737 | } 1738 | 1739 | for (i = 0; i <= T_MASK; i++) counts[i] = 0; 1740 | 1741 | FOR_EACH_HEAP_SLOT(p) 1742 | // danger: allocates memory while walking heap 1743 | if (p->as.basic.flags) { 1744 | int type = BUILTIN_TYPE(p); 1745 | counts[type]++; 1746 | if(type == T_OBJECT){ 1747 | //take class etc. 1748 | cls = rb_class_real_checked(CLASS_OF(p)); 1749 | if(!cls) continue; 1750 | class_name = rb_class_path(cls); 1751 | for(n = RARRAY_LEN(string_prefixes)-1; n >= 0; n--){ 1752 | prefix = rb_check_string_type(RARRAY_PTR(string_prefixes)[n]); 1753 | if(NIL_P(prefix)) continue; 1754 | rb_enc_check(class_name, prefix); 1755 | if (RSTRING_LEN(class_name) < RSTRING_LEN(prefix)) continue; 1756 | if (!memcmp(RSTRING_PTR(class_name), RSTRING_PTR(prefix), RSTRING_LEN(prefix))) 1757 | if(RSTRING_LEN(class_name) == RSTRING_LEN(prefix) || 1758 | RSTRING_PTR(class_name)[RSTRING_LEN(prefix)] == ':'){ 1759 | //class match 1760 | VALUE val = rb_hash_aref(hash, class_name); 1761 | long num; 1762 | if(FIXNUM_P(val)){ 1763 | num = FIX2LONG(val) + 1; 1764 | } else { 1765 | num = 1; 1766 | } 1767 | rb_hash_aset(hash, class_name, LONG2FIX(num)); 1768 | } 1769 | } 1770 | } 1771 | } else { 1772 | freed++; 1773 | } 1774 | FOR_EACH_HEAP_SLOT_END(total) 1775 | 1776 | ygh_int("total_slots", total); 1777 | ygh_int("free_slots", freed); 1778 | yg_cstring("basic_types"); 1779 | yg_map(); 1780 | for (i = 0; i <= T_MASK; i++) { 1781 | if(!counts[i]) continue; 1782 | yg_cstring(rb_type_str((int)i)); 1783 | yg_int(counts[i]); 1784 | } 1785 | yg_map_end(); 1786 | 1787 | yg_cstring("user_types"); 1788 | yg_map(); 1789 | rb_hash_foreach(hash, iterate_user_type_counts, (VALUE)yajl); 1790 | yg_map_end(); 1791 | 1792 | yg_map_end(); //all document 1793 | return hash; 1794 | #undef YAJL 1795 | } 1796 | 1797 | static VALUE 1798 | rb_heapdump_count_objects(VALUE self, VALUE string_prefixes, VALUE do_gc){ 1799 | yajl_gen_config cfg; 1800 | yajl_gen yajl; 1801 | const unsigned char* buf; 1802 | unsigned int len; 1803 | 1804 | rb_check_array_type(string_prefixes); 1805 | 1806 | memset(&cfg, 0, sizeof(cfg)); 1807 | cfg.beautify = true; 1808 | cfg.htmlSafe = true; 1809 | cfg.indentString = " "; 1810 | yajl = yajl_gen_alloc(&cfg,NULL); 1811 | 1812 | heapdump_count_objects_core(yajl, string_prefixes, RTEST(do_gc)); 1813 | 1814 | //flush yajl: 1815 | if(yajl_gen_get_buf(yajl, &buf, &len) == yajl_gen_status_ok){ 1816 | //fwrite(buf, len, 1, ctx->file); 1817 | VALUE res = rb_str_new((char*)buf, len); 1818 | yajl_gen_clear(yajl); 1819 | yajl_gen_free(yajl); 1820 | return res; 1821 | } else { 1822 | return Qnil; 1823 | } 1824 | } 1825 | 1826 | //NOTE: return value must be freed if not null 1827 | static const char* heapdump_count_objects_ex(int return_string, char* first_name, va_list args){ 1828 | yajl_gen_config cfg; 1829 | yajl_gen yajl; 1830 | const unsigned char* buf; 1831 | unsigned int len; 1832 | VALUE string_prefixes; 1833 | 1834 | 1835 | memset(&cfg, 0, sizeof(cfg)); 1836 | cfg.beautify = !return_string; 1837 | // cfg.htmlSafe = true; 1838 | cfg.indentString = " "; 1839 | yajl = yajl_gen_alloc(&cfg,NULL); 1840 | 1841 | string_prefixes = rb_ary_new(); 1842 | while(first_name && first_name[0]){ 1843 | rb_ary_push(string_prefixes, rb_str_new2(first_name)); 1844 | first_name = va_arg(args, char*); 1845 | } 1846 | 1847 | heapdump_count_objects_core(yajl, string_prefixes, false); 1848 | 1849 | //flush yajl: 1850 | if(yajl_gen_get_buf(yajl, &buf, &len) == yajl_gen_status_ok){ 1851 | char* result = NULL; 1852 | if(!return_string){ 1853 | fwrite(buf, len, 1, stderr); 1854 | fprintf(stderr, "\n"); 1855 | } else { 1856 | result = malloc(len); 1857 | memcpy(result, buf, len); 1858 | } 1859 | yajl_gen_clear(yajl); 1860 | yajl_gen_free(yajl); 1861 | return result; //NOTE: that memory is already freed! (but it's usually ok for gdb) 1862 | } 1863 | return NULL; 1864 | } 1865 | 1866 | void heapdump_count_objects_print(char* first_name, ...){ 1867 | va_list args; 1868 | va_start(args, first_name); 1869 | heapdump_count_objects_ex(false, first_name, args); 1870 | va_end(args); 1871 | } 1872 | 1873 | //NOTE: return value must be freed if not null 1874 | const char* heapdump_count_objects_return(char* first_name, ...){ 1875 | va_list args; 1876 | va_start(args, first_name); 1877 | const char* res = heapdump_count_objects_ex(true, first_name, args); 1878 | va_end(args); 1879 | return res; 1880 | } 1881 | 1882 | 1883 | static VALUE rb_heapdump_trigger_int_3(VALUE self){ 1884 | __asm__("int $0x3;"); 1885 | return Qnil; 1886 | } 1887 | 1888 | void Init_heap_dump(){ 1889 | //ruby-internal need to be required before linking us, but just in case.. 1890 | ID require, gem; 1891 | CONST_ID(require, "require"); 1892 | CONST_ID(gem, "gem"); 1893 | CONST_ID(classid, "__classid__"); 1894 | 1895 | rb_require("rubygems"); 1896 | rb_funcall(rb_mKernel, gem, 1, rb_str_new2("yajl-ruby")); 1897 | rb_require("yajl"); 1898 | 1899 | rb_mHeapDumpModule = rb_define_module("HeapDump"); 1900 | rb_define_singleton_method(rb_mHeapDumpModule, "dump_ext", rb_heapdump_dump, 1); 1901 | rb_define_singleton_method(rb_mHeapDumpModule, "count_objects_ext", rb_heapdump_count_objects, 2); 1902 | rb_define_singleton_method(rb_mHeapDumpModule, "int3", rb_heapdump_trigger_int_3, 0); 1903 | 1904 | rb_define_singleton_method(rb_mHeapDumpModule, "verbose", heapdump_verbose, 0); 1905 | rb_define_singleton_method(rb_mHeapDumpModule, "verbose=", heapdump_verbose_setter, 1); 1906 | 1907 | } 1908 | -------------------------------------------------------------------------------- /ext/heap_dump/ruby_io.h: -------------------------------------------------------------------------------- 1 | //FIXME:!!!!! get this file from ruby source! 2 | 3 | 4 | typedef struct { 5 | char *ptr; /* off + len <= capa */ 6 | int off; 7 | int len; 8 | int capa; 9 | } rb_io_buffer_t; 10 | 11 | 12 | //#include "encoding.h" // and this also (rb_encoding + rb_econv_t) 13 | //FIXME: nasty: 14 | // typedef int rb_encoding; 15 | // typedef struct rb_econv rb_econv_t; 16 | 17 | typedef struct rb_io_t { 18 | int fd; /* file descriptor */ 19 | FILE *stdio_file; /* stdio ptr for read/write if available */ 20 | int mode; /* mode flags: FMODE_XXXs */ 21 | rb_pid_t pid; /* child's pid (for pipes) */ 22 | int lineno; /* number of lines read */ 23 | VALUE pathv; /* pathname for file */ 24 | void (*finalize)(struct rb_io_t*,int); /* finalize proc */ 25 | 26 | rb_io_buffer_t wbuf, rbuf; 27 | 28 | VALUE tied_io_for_writing; 29 | 30 | /* 31 | * enc enc2 read action write action 32 | * NULL NULL force_encoding(default_external) write the byte sequence of str 33 | * e1 NULL force_encoding(e1) convert str.encoding to e1 34 | * e1 e2 convert from e2 to e1 convert str.encoding to e2 35 | */ 36 | struct rb_io_enc_t { 37 | rb_encoding *enc; 38 | rb_encoding *enc2; 39 | int ecflags; 40 | VALUE ecopts; 41 | } encs; 42 | 43 | rb_econv_t *readconv; 44 | rb_io_buffer_t cbuf; 45 | 46 | rb_econv_t *writeconv; 47 | VALUE writeconv_asciicompat; 48 | int writeconv_pre_ecflags; 49 | VALUE writeconv_pre_ecopts; 50 | int writeconv_initialized; 51 | 52 | VALUE write_lock; 53 | } rb_io_t; -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.2/fiber.h: -------------------------------------------------------------------------------- 1 | //1.9.2 2 | 3 | #define CAPTURE_JUST_VALID_VM_STACK 1 4 | 5 | enum context_type { 6 | CONTINUATION_CONTEXT = 0, 7 | FIBER_CONTEXT = 1, 8 | ROOT_FIBER_CONTEXT = 2 9 | }; 10 | 11 | typedef struct rb_context_struct { 12 | enum context_type type; 13 | VALUE self; 14 | int argc; 15 | VALUE value; 16 | VALUE *vm_stack; 17 | #ifdef CAPTURE_JUST_VALID_VM_STACK 18 | size_t vm_stack_slen; /* length of stack (head of th->stack) */ 19 | size_t vm_stack_clen; /* length of control frames (tail of th->stack) */ 20 | #endif 21 | VALUE *machine_stack; 22 | VALUE *machine_stack_src; 23 | #ifdef __ia64 24 | VALUE *machine_register_stack; 25 | VALUE *machine_register_stack_src; 26 | int machine_register_stack_size; 27 | #endif 28 | rb_thread_t saved_thread; 29 | rb_jmpbuf_t jmpbuf; 30 | size_t machine_stack_size; 31 | } rb_context_t; 32 | 33 | 34 | 35 | enum fiber_status { 36 | CREATED, 37 | RUNNING, 38 | TERMINATED 39 | }; 40 | 41 | typedef struct rb_fiber_struct { 42 | rb_context_t cont; 43 | VALUE prev; 44 | enum fiber_status status; 45 | struct rb_fiber_struct *prev_fiber; 46 | struct rb_fiber_struct *next_fiber; 47 | } rb_fiber_t; 48 | 49 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.2/gc_internal.h: -------------------------------------------------------------------------------- 1 | //extracted from ruby 1.9.2 p290, should be compatible with all 1.9.2's 2 | 3 | //TODO: does conflict with ruby framework? 4 | #include "ruby/re.h" 5 | 6 | //FIXME: this should be autoextracted from ruby 7 | // see how this is done in ruby-internal gem 8 | 9 | 10 | #define MARK_STACK_MAX 1024 11 | 12 | #ifndef CALC_EXACT_MALLOC_SIZE 13 | #define CALC_EXACT_MALLOC_SIZE 0 14 | #endif 15 | 16 | typedef struct RVALUE { 17 | union { 18 | struct { 19 | VALUE flags; /* always 0 for freed obj */ 20 | struct RVALUE *next; 21 | } free; 22 | struct RBasic basic; 23 | struct RObject object; 24 | struct RClass klass; 25 | struct RFloat flonum; 26 | struct RString string; 27 | struct RArray array; 28 | struct RRegexp regexp; 29 | struct RHash hash; 30 | struct RData data; 31 | struct RTypedData typeddata; 32 | struct RStruct rstruct; 33 | struct RBignum bignum; 34 | struct RFile file; 35 | struct RNode node; 36 | struct RMatch match; 37 | struct RRational rational; 38 | struct RComplex complex; 39 | } as; 40 | #ifdef GC_DEBUG 41 | const char *file; 42 | int line; 43 | #endif 44 | } RVALUE; 45 | 46 | typedef struct gc_profile_record { 47 | double gc_time; 48 | double gc_mark_time; 49 | double gc_sweep_time; 50 | double gc_invoke_time; 51 | 52 | size_t heap_use_slots; 53 | size_t heap_live_objects; 54 | size_t heap_free_objects; 55 | size_t heap_total_objects; 56 | size_t heap_use_size; 57 | size_t heap_total_size; 58 | 59 | int have_finalize; 60 | int is_marked; 61 | 62 | size_t allocate_increase; 63 | size_t allocate_limit; 64 | } gc_profile_record; 65 | 66 | struct heaps_slot { 67 | void *membase; 68 | RVALUE *slot; 69 | size_t limit; 70 | int finalize_flag; 71 | }; 72 | 73 | struct heaps_header { 74 | struct heaps_slot *base; 75 | uintptr_t *bits; 76 | }; 77 | 78 | struct gc_list { 79 | VALUE *varptr; 80 | struct gc_list *next; 81 | }; 82 | 83 | 84 | // 1.9.2-p290: 85 | typedef struct rb_objspace { 86 | struct { 87 | size_t limit; 88 | size_t increase; 89 | #if CALC_EXACT_MALLOC_SIZE 90 | size_t allocated_size; 91 | size_t allocations; 92 | #endif 93 | } malloc_params; 94 | struct { 95 | size_t increment; 96 | struct heaps_slot *ptr; 97 | size_t length; 98 | size_t used; 99 | RVALUE *freelist; 100 | RVALUE *range[2]; 101 | RVALUE *freed; 102 | } heap; 103 | struct { 104 | int dont_gc; 105 | int during_gc; 106 | } flags; 107 | struct { 108 | st_table *table; 109 | RVALUE *deferred; 110 | } final; 111 | struct { 112 | VALUE buffer[MARK_STACK_MAX]; 113 | VALUE *ptr; 114 | int overflow; 115 | } markstack; 116 | struct { 117 | int run; 118 | gc_profile_record *record; 119 | size_t count; 120 | size_t size; 121 | double invoke_time; 122 | } profile; 123 | struct gc_list *global_list; 124 | unsigned int count; 125 | int gc_stress; 126 | } rb_objspace_t; 127 | 128 | // 129 | #define RANY(o) ((RVALUE*)(o)) 130 | 131 | #define malloc_limit objspace->malloc_params.limit 132 | #define malloc_increase objspace->malloc_params.increase 133 | #define heaps objspace->heap.ptr 134 | #define heaps_length objspace->heap.length 135 | #define heaps_used objspace->heap.used 136 | #define lomem objspace->heap.range[0] 137 | #define himem objspace->heap.range[1] 138 | #define heaps_inc objspace->heap.increment 139 | #define heaps_freed objspace->heap.freed 140 | #define dont_gc objspace->flags.dont_gc 141 | #define during_gc objspace->flags.during_gc 142 | #define finalizing objspace->flags.finalizing 143 | #define finalizer_table objspace->final.table 144 | #define deferred_final_list objspace->final.deferred 145 | #define mark_stack objspace->markstack.buffer 146 | #define mark_stack_ptr objspace->markstack.ptr 147 | #define mark_stack_overflow objspace->markstack.overflow 148 | #define global_List objspace->global_list 149 | 150 | 151 | //1.9.2-p290 152 | static inline int 153 | is_pointer_to_heap(rb_objspace_t *objspace, void *ptr) 154 | { 155 | register RVALUE *p = RANY(ptr); 156 | register struct heaps_slot *heap; 157 | register size_t hi, lo, mid; 158 | 159 | if (p < lomem || p > himem) return FALSE; 160 | if ((VALUE)p % sizeof(RVALUE) != 0) return FALSE; 161 | 162 | /* check if p looks like a pointer using bsearch*/ 163 | lo = 0; 164 | hi = heaps_used; 165 | while (lo < hi) { 166 | mid = (lo + hi) / 2; 167 | heap = &heaps[mid]; 168 | if (heap->slot <= p) { 169 | if (p < heap->slot + heap->limit) 170 | return TRUE; 171 | lo = mid + 1; 172 | } 173 | else { 174 | hi = mid; 175 | } 176 | } 177 | return FALSE; 178 | } 179 | 180 | #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) {\ 181 | RVALUE *p = heaps[i].slot; RVALUE *pend = p + heaps[i].limit;\ 182 | if(!p || p < heaps[i].membase) continue;\ 183 | for (; p < pend; p++) { 184 | #define FOR_EACH_HEAP_SLOT_END(total) } total += heaps[i].limit; } 185 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.2/internal_constant.h: -------------------------------------------------------------------------------- 1 | //from 1.9.2 2 | typedef enum { 3 | CONST_PUBLIC = 0x00, 4 | CONST_PRIVATE = 0x01 5 | } rb_const_flag_t; 6 | 7 | typedef struct rb_const_entry_struct { 8 | rb_const_flag_t flag; 9 | VALUE value; /* should be mark */ 10 | } rb_const_entry_t; 11 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.2/internal_typed_data.h: -------------------------------------------------------------------------------- 1 | //FIXME: autogen this from ruby (this copied from 1.9.2p290) 2 | struct thgroup { 3 | int enclosed; 4 | VALUE group; 5 | }; 6 | 7 | 8 | // 9 | 10 | struct enumerator { 11 | VALUE obj; 12 | ID meth; 13 | VALUE args; 14 | VALUE fib; 15 | VALUE dst; 16 | VALUE lookahead; 17 | VALUE feedvalue; 18 | VALUE stop_exc; 19 | }; 20 | 21 | struct generator { 22 | VALUE proc; 23 | }; 24 | 25 | struct yielder { 26 | VALUE proc; 27 | }; 28 | 29 | // 30 | struct METHOD { 31 | VALUE recv; 32 | VALUE rclass; 33 | ID id; 34 | rb_method_entry_t me; 35 | }; 36 | 37 | // 38 | 39 | #define METHOD_DEFINITIONP(m) m->me.def 40 | 41 | #define HAVE_RB_CLASS_TBL 1 42 | 43 | inline st_table * rb_get_class_tbl(){ 44 | //class.c: 45 | extern st_table *rb_class_tbl; 46 | return rb_class_tbl; 47 | } 48 | 49 | #define HAVE_RB_GLOBAL_TBL 1 50 | inline st_table * rb_get_global_tbl(){ 51 | //class.c: 52 | extern st_table *rb_global_tbl; 53 | return rb_global_tbl; 54 | } 55 | #define gvar_getter_t rb_gvar_getter_t 56 | #define gvar_setter_t rb_gvar_setter_t 57 | #define gvar_marker_t rb_gvar_marker_t 58 | 59 | struct trace_var { 60 | int removed; 61 | void (*func)(VALUE arg, VALUE val); 62 | VALUE data; 63 | struct trace_var *next; 64 | }; 65 | 66 | //struct global_variable { 67 | struct rb_global_variable { 68 | int counter; 69 | void *data; 70 | gvar_getter_t *getter; 71 | gvar_setter_t *setter; 72 | gvar_marker_t *marker; 73 | int block_trace; 74 | struct trace_var *trace; 75 | }; 76 | 77 | extern VALUE ruby_engine_name; -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.3/fiber.h: -------------------------------------------------------------------------------- 1 | //1.9.3 2 | //from cont.c: 3 | 4 | #define CAPTURE_JUST_VALID_VM_STACK 1 5 | 6 | enum context_type { 7 | CONTINUATION_CONTEXT = 0, 8 | FIBER_CONTEXT = 1, 9 | ROOT_FIBER_CONTEXT = 2 10 | }; 11 | 12 | typedef struct rb_context_struct { 13 | enum context_type type; 14 | VALUE self; 15 | int argc; 16 | VALUE value; 17 | VALUE *vm_stack; 18 | #ifdef CAPTURE_JUST_VALID_VM_STACK 19 | size_t vm_stack_slen; /* length of stack (head of th->stack) */ 20 | size_t vm_stack_clen; /* length of control frames (tail of th->stack) */ 21 | #endif 22 | VALUE *machine_stack; 23 | VALUE *machine_stack_src; 24 | #ifdef __ia64 25 | VALUE *machine_register_stack; 26 | VALUE *machine_register_stack_src; 27 | int machine_register_stack_size; 28 | #endif 29 | rb_thread_t saved_thread; 30 | rb_jmpbuf_t jmpbuf; 31 | size_t machine_stack_size; 32 | } rb_context_t; 33 | 34 | enum fiber_status { 35 | CREATED, 36 | RUNNING, 37 | TERMINATED 38 | }; 39 | 40 | typedef struct rb_fiber_struct { 41 | rb_context_t cont; 42 | VALUE prev; 43 | enum fiber_status status; 44 | struct rb_fiber_struct *prev_fiber; 45 | struct rb_fiber_struct *next_fiber; 46 | #if FIBER_USE_NATIVE 47 | #ifdef _WIN32 48 | void *fib_handle; 49 | #else 50 | ucontext_t context; 51 | #endif 52 | #endif 53 | } rb_fiber_t; 54 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.3/gc_internal.h: -------------------------------------------------------------------------------- 1 | //TODO: does conflict with ruby framework? 2 | #include "ruby/re.h" 3 | 4 | //gc.c 5 | 6 | #define MARK_STACK_MAX 1024 7 | // 8 | /* for GC profile */ 9 | #define GC_PROFILE_MORE_DETAIL 0 10 | typedef struct gc_profile_record { 11 | double gc_time; 12 | double gc_mark_time; 13 | double gc_sweep_time; 14 | double gc_invoke_time; 15 | 16 | size_t heap_use_slots; 17 | size_t heap_live_objects; 18 | size_t heap_free_objects; 19 | size_t heap_total_objects; 20 | size_t heap_use_size; 21 | size_t heap_total_size; 22 | 23 | int have_finalize; 24 | int is_marked; 25 | 26 | size_t allocate_increase; 27 | size_t allocate_limit; 28 | } gc_profile_record; 29 | // 30 | 31 | typedef struct RVALUE { 32 | union { 33 | struct { 34 | VALUE flags; /* always 0 for freed obj */ 35 | struct RVALUE *next; 36 | } free; 37 | struct RBasic basic; 38 | struct RObject object; 39 | struct RClass klass; 40 | struct RFloat flonum; 41 | struct RString string; 42 | struct RArray array; 43 | struct RRegexp regexp; 44 | struct RHash hash; 45 | struct RData data; 46 | struct RTypedData typeddata; 47 | struct RStruct rstruct; 48 | struct RBignum bignum; 49 | struct RFile file; 50 | struct RNode node; 51 | struct RMatch match; 52 | struct RRational rational; 53 | struct RComplex complex; 54 | } as; 55 | #ifdef GC_DEBUG 56 | const char *file; 57 | int line; 58 | #endif 59 | } RVALUE; 60 | 61 | #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) 62 | #pragma pack(pop) 63 | #endif 64 | 65 | struct heaps_slot { 66 | void *membase; 67 | RVALUE *slot; 68 | size_t limit; 69 | struct heaps_slot *next; 70 | struct heaps_slot *prev; 71 | }; 72 | 73 | struct sorted_heaps_slot { 74 | RVALUE *start; 75 | RVALUE *end; 76 | struct heaps_slot *slot; 77 | }; 78 | 79 | struct gc_list { 80 | VALUE *varptr; 81 | struct gc_list *next; 82 | }; 83 | 84 | #define CALC_EXACT_MALLOC_SIZE 0 85 | 86 | typedef struct rb_objspace { 87 | struct { 88 | size_t limit; 89 | size_t increase; 90 | #if CALC_EXACT_MALLOC_SIZE 91 | size_t allocated_size; 92 | size_t allocations; 93 | #endif 94 | } malloc_params; 95 | struct { 96 | size_t increment; 97 | struct heaps_slot *ptr; 98 | struct heaps_slot *sweep_slots; 99 | struct sorted_heaps_slot *sorted; 100 | size_t length; 101 | size_t used; 102 | RVALUE *freelist; 103 | RVALUE *range[2]; 104 | RVALUE *freed; 105 | size_t live_num; 106 | size_t free_num; 107 | size_t free_min; 108 | size_t final_num; 109 | size_t do_heap_free; 110 | } heap; 111 | struct { 112 | int dont_gc; 113 | int dont_lazy_sweep; 114 | int during_gc; 115 | } flags; 116 | struct { 117 | st_table *table; 118 | RVALUE *deferred; 119 | } final; 120 | struct { 121 | VALUE buffer[MARK_STACK_MAX]; 122 | VALUE *ptr; 123 | int overflow; 124 | } markstack; 125 | struct { 126 | int run; 127 | gc_profile_record *record; 128 | size_t count; 129 | size_t size; 130 | double invoke_time; 131 | } profile; 132 | struct gc_list *global_list; 133 | size_t count; 134 | int gc_stress; 135 | } rb_objspace_t; 136 | 137 | // 138 | 139 | #define malloc_limit objspace->malloc_params.limit 140 | #define malloc_increase objspace->malloc_params.increase 141 | #define heaps objspace->heap.ptr 142 | #define heaps_length objspace->heap.length 143 | #define heaps_used objspace->heap.used 144 | #define freelist objspace->heap.freelist 145 | #define lomem objspace->heap.range[0] 146 | #define himem objspace->heap.range[1] 147 | #define heaps_inc objspace->heap.increment 148 | #define heaps_freed objspace->heap.freed 149 | #define dont_gc objspace->flags.dont_gc 150 | #define during_gc objspace->flags.during_gc 151 | #define finalizer_table objspace->final.table 152 | #define deferred_final_list objspace->final.deferred 153 | #define mark_stack objspace->markstack.buffer 154 | #define mark_stack_ptr objspace->markstack.ptr 155 | #define mark_stack_overflow objspace->markstack.overflow 156 | #define global_List objspace->global_list 157 | #define ruby_gc_stress objspace->gc_stress 158 | #define initial_malloc_limit initial_params.initial_malloc_limit 159 | #define initial_heap_min_slots initial_params.initial_heap_min_slots 160 | #define initial_free_min initial_params.initial_free_min 161 | 162 | // 163 | #define RANY(o) ((RVALUE*)(o)) 164 | // 165 | static inline int 166 | is_pointer_to_heap(rb_objspace_t *objspace, void *ptr) 167 | { 168 | register RVALUE *p = RANY(ptr); 169 | register struct sorted_heaps_slot *heap; 170 | register size_t hi, lo, mid; 171 | 172 | if (p < lomem || p > himem) return FALSE; 173 | if ((VALUE)p % sizeof(RVALUE) != 0) return FALSE; 174 | 175 | /* check if p looks like a pointer using bsearch*/ 176 | lo = 0; 177 | hi = heaps_used; 178 | while (lo < hi) { 179 | mid = (lo + hi) / 2; 180 | heap = &objspace->heap.sorted[mid]; 181 | if (heap->start <= p) { 182 | if (p < heap->end) 183 | return TRUE; 184 | lo = mid + 1; 185 | } 186 | else { 187 | hi = mid; 188 | } 189 | } 190 | return FALSE; 191 | } 192 | 193 | #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) {\ 194 | RVALUE *p = objspace->heap.sorted[i].start, *pend = objspace->heap.sorted[i].end;\ 195 | if(!p) continue;\ 196 | for (; p < pend; p++) { 197 | #define FOR_EACH_HEAP_SLOT_END(total) } total += objspace->heap.sorted[i].slot->limit; } 198 | 199 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-1.9.3/internal_typed_data.h: -------------------------------------------------------------------------------- 1 | //FIXME: autogen this from ruby (this copied from 1.9.3p194) 2 | 3 | // thread.c: 4 | struct thgroup { 5 | int enclosed; 6 | VALUE group; 7 | }; 8 | 9 | // enumerator.c: 10 | struct enumerator { 11 | VALUE obj; 12 | ID meth; 13 | VALUE args; 14 | VALUE fib; 15 | VALUE dst; 16 | VALUE lookahead; 17 | VALUE feedvalue; 18 | VALUE stop_exc; 19 | }; 20 | 21 | // 22 | struct generator { 23 | VALUE proc; 24 | }; 25 | 26 | struct yielder { 27 | VALUE proc; 28 | }; 29 | 30 | 31 | // proc.c: 32 | struct METHOD { 33 | VALUE recv; 34 | VALUE rclass; 35 | ID id; 36 | rb_method_entry_t *me; 37 | struct unlinked_method_entry_list_entry *ume; 38 | }; 39 | 40 | // 41 | #define METHOD_DEFINITIONP(m) (m->me ? m->me->def : NULL) 42 | 43 | //class.c: 44 | #define HAVE_RB_CLASS_TBL 1 45 | //For som reason this fails to link directly on 1.9.3 :( 46 | 47 | //HACK: 48 | // otool -L `which ruby` 49 | // /Users/vasfed/.rvm/rubies/ruby-1.9.3-p194/bin/ruby: 50 | // @executable_path/../lib/libruby.1.9.1.dylib (compatibility version 1.9.1, current version 1.9.1) 51 | // nm ~/.rvm/rubies/ruby-1.9.3-p194/lib/libruby.1.9.1.dylib | grep rb_class_tbl 52 | // 000000000024be28 s _rb_class_tbl 53 | // 00000000000b311c T _rb_intern 54 | // 000000000024bd38 S _rb_mKernel 55 | 56 | #include 57 | 58 | inline st_table * rb_get_class_tbl(){ 59 | Dl_info info; 60 | if(!dladdr(rb_intern, &info) || !info.dli_fname){ 61 | return NULL; 62 | } 63 | void* image = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL); 64 | // printf("Image is %p, addr is %p (%p rel)\n", image, rb_intern, ((void*)rb_intern - image)); 65 | if(image) 66 | { 67 | void* tbl = dlsym(image, "_rb_class_tbl"); 68 | dlclose(image); 69 | if(tbl) 70 | return tbl; 71 | } 72 | 73 | //TODO: parse sym table and calculate address? 74 | 75 | return NULL; 76 | } 77 | 78 | #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current())) 79 | 80 | //FIXME: get global const for it: rb_define_global_const("RUBY_ENGINE", ruby_engine_name = MKSTR(engine)); 81 | #define ruby_engine_name Qnil 82 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0/fiber.h: -------------------------------------------------------------------------------- 1 | #define CAPTURE_JUST_VALID_VM_STACK 1 2 | 3 | enum context_type { 4 | CONTINUATION_CONTEXT = 0, 5 | FIBER_CONTEXT = 1, 6 | ROOT_FIBER_CONTEXT = 2 7 | }; 8 | 9 | typedef struct rb_context_struct { 10 | enum context_type type; 11 | VALUE self; 12 | int argc; 13 | VALUE value; 14 | VALUE *vm_stack; 15 | #ifdef CAPTURE_JUST_VALID_VM_STACK 16 | size_t vm_stack_slen; /* length of stack (head of th->stack) */ 17 | size_t vm_stack_clen; /* length of control frames (tail of th->stack) */ 18 | #endif 19 | VALUE *machine_stack; 20 | VALUE *machine_stack_src; 21 | #ifdef __ia64 22 | VALUE *machine_register_stack; 23 | VALUE *machine_register_stack_src; 24 | int machine_register_stack_size; 25 | #endif 26 | rb_thread_t saved_thread; 27 | rb_jmpbuf_t jmpbuf; 28 | size_t machine_stack_size; 29 | } rb_context_t; 30 | 31 | enum fiber_status { 32 | CREATED, 33 | RUNNING, 34 | TERMINATED 35 | }; 36 | 37 | #if FIBER_USE_NATIVE && !defined(_WIN32) 38 | #define MAX_MAHINE_STACK_CACHE 10 39 | static int machine_stack_cache_index = 0; 40 | typedef struct machine_stack_cache_struct { 41 | void *ptr; 42 | size_t size; 43 | } machine_stack_cache_t; 44 | static machine_stack_cache_t machine_stack_cache[MAX_MAHINE_STACK_CACHE]; 45 | static machine_stack_cache_t terminated_machine_stack; 46 | #endif 47 | 48 | typedef struct rb_fiber_struct { 49 | rb_context_t cont; 50 | VALUE prev; 51 | enum fiber_status status; 52 | struct rb_fiber_struct *prev_fiber; 53 | struct rb_fiber_struct *next_fiber; 54 | /* If a fiber invokes "transfer", 55 | * then this fiber can't "resume" any more after that. 56 | * You shouldn't mix "transfer" and "resume". 57 | */ 58 | int transfered; 59 | 60 | #if FIBER_USE_NATIVE 61 | #ifdef _WIN32 62 | void *fib_handle; 63 | #else 64 | ucontext_t context; 65 | #endif 66 | #endif 67 | } rb_fiber_t; 68 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0/gc_internal.h: -------------------------------------------------------------------------------- 1 | #include "ruby/re.h" 2 | 3 | #ifndef GC_PROFILE_MORE_DETAIL 4 | #define GC_PROFILE_MORE_DETAIL 0 5 | #endif 6 | 7 | typedef struct gc_profile_record { 8 | double gc_time; 9 | double gc_invoke_time; 10 | 11 | size_t heap_total_objects; 12 | size_t heap_use_size; 13 | size_t heap_total_size; 14 | 15 | int is_marked; 16 | 17 | #if GC_PROFILE_MORE_DETAIL 18 | double gc_mark_time; 19 | double gc_sweep_time; 20 | 21 | size_t heap_use_slots; 22 | size_t heap_live_objects; 23 | size_t heap_free_objects; 24 | 25 | int have_finalize; 26 | 27 | size_t allocate_increase; 28 | size_t allocate_limit; 29 | #endif 30 | } gc_profile_record; 31 | 32 | #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) 33 | #pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */ 34 | #endif 35 | 36 | typedef struct RVALUE { 37 | union { 38 | struct { 39 | VALUE flags; /* always 0 for freed obj */ 40 | struct RVALUE *next; 41 | } free; 42 | struct RBasic basic; 43 | struct RObject object; 44 | struct RClass klass; 45 | struct RFloat flonum; 46 | struct RString string; 47 | struct RArray array; 48 | struct RRegexp regexp; 49 | struct RHash hash; 50 | struct RData data; 51 | struct RTypedData typeddata; 52 | struct RStruct rstruct; 53 | struct RBignum bignum; 54 | struct RFile file; 55 | struct RNode node; 56 | struct RMatch match; 57 | struct RRational rational; 58 | struct RComplex complex; 59 | } as; 60 | #ifdef GC_DEBUG 61 | const char *file; 62 | int line; 63 | #endif 64 | } RVALUE; 65 | 66 | #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) 67 | #pragma pack(pop) 68 | #endif 69 | 70 | struct heaps_slot { 71 | struct heaps_header *header; 72 | uintptr_t *bits; 73 | RVALUE *freelist; 74 | struct heaps_slot *next; 75 | struct heaps_slot *prev; 76 | struct heaps_slot *free_next; 77 | }; 78 | 79 | struct heaps_header { 80 | struct heaps_slot *base; 81 | uintptr_t *bits; 82 | RVALUE *start; 83 | RVALUE *end; 84 | size_t limit; 85 | }; 86 | 87 | struct heaps_free_bitmap { 88 | struct heaps_free_bitmap *next; 89 | }; 90 | 91 | struct gc_list { 92 | VALUE *varptr; 93 | struct gc_list *next; 94 | }; 95 | 96 | #define STACK_CHUNK_SIZE 500 97 | 98 | typedef struct stack_chunk { 99 | VALUE data[STACK_CHUNK_SIZE]; 100 | struct stack_chunk *next; 101 | } stack_chunk_t; 102 | 103 | typedef struct mark_stack { 104 | stack_chunk_t *chunk; 105 | stack_chunk_t *cache; 106 | size_t index; 107 | size_t limit; 108 | size_t cache_size; 109 | size_t unused_cache_size; 110 | } mark_stack_t; 111 | 112 | #ifndef CALC_EXACT_MALLOC_SIZE 113 | #define CALC_EXACT_MALLOC_SIZE 0 114 | #endif 115 | 116 | typedef struct rb_objspace { 117 | struct { 118 | size_t limit; 119 | size_t increase; 120 | #if CALC_EXACT_MALLOC_SIZE 121 | size_t allocated_size; 122 | size_t allocations; 123 | #endif 124 | } malloc_params; 125 | struct { 126 | size_t increment; 127 | struct heaps_slot *ptr; 128 | struct heaps_slot *sweep_slots; 129 | struct heaps_slot *free_slots; 130 | struct heaps_header **sorted; 131 | size_t length; 132 | size_t used; 133 | struct heaps_free_bitmap *free_bitmap; 134 | RVALUE *range[2]; 135 | struct heaps_header *freed; 136 | size_t free_num; 137 | size_t free_min; 138 | size_t final_num; 139 | size_t do_heap_free; 140 | } heap; 141 | struct { 142 | int dont_gc; 143 | int dont_lazy_sweep; 144 | int during_gc; 145 | rb_atomic_t finalizing; 146 | } flags; 147 | struct { 148 | st_table *table; 149 | RVALUE *deferred; 150 | } final; 151 | mark_stack_t mark_stack; 152 | struct { 153 | int run; 154 | gc_profile_record *record; 155 | size_t count; 156 | size_t size; 157 | double invoke_time; 158 | } profile; 159 | struct gc_list *global_list; 160 | size_t count; 161 | size_t total_allocated_object_num; 162 | size_t total_freed_object_num; 163 | int gc_stress; 164 | 165 | struct mark_func_data_struct { 166 | void *data; 167 | void (*mark_func)(VALUE v, void *data); 168 | } *mark_func_data; 169 | } rb_objspace_t; 170 | 171 | #define malloc_limit objspace->malloc_params.limit 172 | #define malloc_increase objspace->malloc_params.increase 173 | #define heaps objspace->heap.ptr 174 | #define heaps_length objspace->heap.length 175 | #define heaps_used objspace->heap.used 176 | #define lomem objspace->heap.range[0] 177 | #define himem objspace->heap.range[1] 178 | #define heaps_inc objspace->heap.increment 179 | #define heaps_freed objspace->heap.freed 180 | #define dont_gc objspace->flags.dont_gc 181 | #define during_gc objspace->flags.during_gc 182 | #define finalizing objspace->flags.finalizing 183 | #define finalizer_table objspace->final.table 184 | #define deferred_final_list objspace->final.deferred 185 | #define global_List objspace->global_list 186 | #define ruby_gc_stress objspace->gc_stress 187 | #define initial_malloc_limit initial_params.initial_malloc_limit 188 | #define initial_heap_min_slots initial_params.initial_heap_min_slots 189 | #define initial_free_min initial_params.initial_free_min 190 | 191 | #define is_lazy_sweeping(objspace) ((objspace)->heap.sweep_slots != 0) 192 | 193 | #define nonspecial_obj_id(obj) (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG) 194 | 195 | #define RANY(o) ((RVALUE*)(o)) 196 | #define has_free_object (objspace->heap.free_slots && objspace->heap.free_slots->freelist) 197 | 198 | #define HEAP_HEADER(p) ((struct heaps_header *)(p)) 199 | #define GET_HEAP_HEADER(x) (HEAP_HEADER((uintptr_t)(x) & ~(HEAP_ALIGN_MASK))) 200 | #define GET_HEAP_SLOT(x) (GET_HEAP_HEADER(x)->base) 201 | #define GET_HEAP_BITMAP(x) (GET_HEAP_HEADER(x)->bits) 202 | #define NUM_IN_SLOT(p) (((uintptr_t)(p) & HEAP_ALIGN_MASK)/sizeof(RVALUE)) 203 | #define BITMAP_INDEX(p) (NUM_IN_SLOT(p) / (sizeof(uintptr_t) * CHAR_BIT)) 204 | #define BITMAP_OFFSET(p) (NUM_IN_SLOT(p) & ((sizeof(uintptr_t) * CHAR_BIT)-1)) 205 | #define MARKED_IN_BITMAP(bits, p) (bits[BITMAP_INDEX(p)] & ((uintptr_t)1 << BITMAP_OFFSET(p))) 206 | // 207 | #define RANY(o) ((RVALUE*)(o)) 208 | // 209 | 210 | static inline int 211 | is_pointer_to_heap(rb_objspace_t *objspace, void *ptr) 212 | { 213 | register RVALUE *p = RANY(ptr); 214 | register struct heaps_header *heap; 215 | register size_t hi, lo, mid; 216 | 217 | if (p < lomem || p > himem) return FALSE; 218 | if ((VALUE)p % sizeof(RVALUE) != 0) return FALSE; 219 | 220 | /* check if p looks like a pointer using bsearch*/ 221 | lo = 0; 222 | hi = heaps_used; 223 | while (lo < hi) { 224 | mid = (lo + hi) / 2; 225 | heap = objspace->heap.sorted[mid]; 226 | if (heap->start <= p) { 227 | if (p < heap->end) 228 | return TRUE; 229 | lo = mid + 1; 230 | } 231 | else { 232 | hi = mid; 233 | } 234 | } 235 | return FALSE; 236 | } 237 | 238 | //from count_objects: 239 | #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) { RVALUE *p, *pend;\ 240 | p = objspace->heap.sorted[i]->start; pend = p + objspace->heap.sorted[i]->limit;\ 241 | for (;p < pend; p++) { 242 | 243 | #define FOR_EACH_HEAP_SLOT_END(total) } total += objspace->heap.sorted[i]->limit; } 244 | 245 | #define NODE_OPTBLOCK 1000000 //FIXME 246 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0/internal_typed_data.h: -------------------------------------------------------------------------------- 1 | //FIXME: autogen this from ruby (this copied from 1.9.3p194) 2 | 3 | // thread.c: 4 | struct thgroup { 5 | int enclosed; 6 | VALUE group; 7 | }; 8 | 9 | // enumerator.c: 10 | struct enumerator { 11 | VALUE obj; 12 | ID meth; 13 | VALUE args; 14 | VALUE fib; 15 | VALUE dst; 16 | VALUE lookahead; 17 | VALUE feedvalue; 18 | VALUE stop_exc; 19 | }; 20 | 21 | // 22 | struct generator { 23 | VALUE proc; 24 | }; 25 | 26 | struct yielder { 27 | VALUE proc; 28 | }; 29 | 30 | 31 | // proc.c: 32 | struct METHOD { 33 | VALUE recv; 34 | VALUE rclass; 35 | VALUE defined_class; //FIXME: dump this 36 | ID id; 37 | rb_method_entry_t *me; 38 | struct unlinked_method_entry_list_entry *ume; 39 | }; 40 | 41 | // 42 | #define METHOD_DEFINITIONP(m) (m->me ? m->me->def : NULL) 43 | 44 | //class.c: 45 | #define HAVE_RB_CLASS_TBL 1 46 | //For som reason this fails to link directly on 1.9.3 :( 47 | 48 | //HACK: 49 | #include 50 | 51 | inline st_table * rb_get_class_tbl(){ 52 | Dl_info info; 53 | void* image; 54 | if(!dladdr(rb_intern, &info) || !info.dli_fname){ 55 | return NULL; 56 | } 57 | image = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL); 58 | // printf("Image is %p, addr is %p (%p rel)\n", image, rb_intern, ((void*)rb_intern - image)); 59 | if(image) 60 | { 61 | void* tbl = dlsym(image, "_rb_class_tbl"); 62 | dlclose(image); 63 | if(tbl) 64 | return tbl; 65 | } 66 | 67 | //TODO: parse sym table and calculate address? 68 | 69 | return NULL; 70 | } 71 | 72 | #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current())) 73 | #define GET_THREAD() ruby_current_thread 74 | 75 | //FIXME: get global const for it: rb_define_global_const("RUBY_ENGINE", ruby_engine_name = MKSTR(engine)); 76 | #define ruby_engine_name Qnil 77 | 78 | #define ID_ALLOCATOR 0 79 | 80 | //vm_trace.c 81 | typedef enum { 82 | RUBY_HOOK_FLAG_SAFE = 0x01, 83 | RUBY_HOOK_FLAG_DELETED = 0x02, 84 | RUBY_HOOK_FLAG_RAW_ARG = 0x04 85 | } rb_hook_flag_t; 86 | typedef struct rb_event_hook_struct { 87 | rb_hook_flag_t hook_flags; 88 | rb_event_flag_t events; 89 | rb_event_hook_func_t func; 90 | VALUE data; 91 | struct rb_event_hook_struct *next; 92 | } rb_event_hook_t; 93 | 94 | //vm_backtrace.c 95 | inline static int 96 | calc_lineno(const rb_iseq_t *iseq, const VALUE *pc) 97 | { 98 | return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded); 99 | } 100 | 101 | int rb_vm_get_sourceline(const rb_control_frame_t * cfp){ 102 | int lineno = 0; 103 | const rb_iseq_t *iseq = cfp->iseq; 104 | 105 | if (RUBY_VM_NORMAL_ISEQ_P(iseq)) { 106 | lineno = calc_lineno(cfp->iseq, cfp->pc); 107 | } 108 | return lineno; 109 | } 110 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0_preview1/fiber.h: -------------------------------------------------------------------------------- 1 | #define CAPTURE_JUST_VALID_VM_STACK 1 2 | 3 | enum context_type { 4 | CONTINUATION_CONTEXT = 0, 5 | FIBER_CONTEXT = 1, 6 | ROOT_FIBER_CONTEXT = 2 7 | }; 8 | 9 | typedef struct rb_context_struct { 10 | enum context_type type; 11 | VALUE self; 12 | int argc; 13 | VALUE value; 14 | VALUE *vm_stack; 15 | #ifdef CAPTURE_JUST_VALID_VM_STACK 16 | size_t vm_stack_slen; /* length of stack (head of th->stack) */ 17 | size_t vm_stack_clen; /* length of control frames (tail of th->stack) */ 18 | #endif 19 | VALUE *machine_stack; 20 | VALUE *machine_stack_src; 21 | #ifdef __ia64 22 | VALUE *machine_register_stack; 23 | VALUE *machine_register_stack_src; 24 | int machine_register_stack_size; 25 | #endif 26 | rb_thread_t saved_thread; 27 | rb_jmpbuf_t jmpbuf; 28 | size_t machine_stack_size; 29 | } rb_context_t; 30 | 31 | enum fiber_status { 32 | CREATED, 33 | RUNNING, 34 | TERMINATED 35 | }; 36 | 37 | #if FIBER_USE_NATIVE && !defined(_WIN32) 38 | #define MAX_MAHINE_STACK_CACHE 10 39 | static int machine_stack_cache_index = 0; 40 | typedef struct machine_stack_cache_struct { 41 | void *ptr; 42 | size_t size; 43 | } machine_stack_cache_t; 44 | static machine_stack_cache_t machine_stack_cache[MAX_MAHINE_STACK_CACHE]; 45 | static machine_stack_cache_t terminated_machine_stack; 46 | #endif 47 | 48 | typedef struct rb_fiber_struct { 49 | rb_context_t cont; 50 | VALUE prev; 51 | enum fiber_status status; 52 | struct rb_fiber_struct *prev_fiber; 53 | struct rb_fiber_struct *next_fiber; 54 | /* If a fiber invokes "transfer", 55 | * then this fiber can't "resume" any more after that. 56 | * You shouldn't mix "transfer" and "resume". 57 | */ 58 | int transfered; 59 | 60 | #if FIBER_USE_NATIVE 61 | #ifdef _WIN32 62 | void *fib_handle; 63 | #else 64 | ucontext_t context; 65 | #endif 66 | #endif 67 | } rb_fiber_t; 68 | -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0_preview1/gc_internal.h: -------------------------------------------------------------------------------- 1 | #include "ruby/re.h" 2 | 3 | #ifndef GC_PROFILE_MORE_DETAIL 4 | #define GC_PROFILE_MORE_DETAIL 0 5 | #endif 6 | 7 | typedef struct gc_profile_record { 8 | double gc_time; 9 | double gc_invoke_time; 10 | 11 | size_t heap_total_objects; 12 | size_t heap_use_size; 13 | size_t heap_total_size; 14 | 15 | int is_marked; 16 | 17 | #if GC_PROFILE_MORE_DETAIL 18 | double gc_mark_time; 19 | double gc_sweep_time; 20 | 21 | size_t heap_use_slots; 22 | size_t heap_live_objects; 23 | size_t heap_free_objects; 24 | 25 | int have_finalize; 26 | 27 | size_t allocate_increase; 28 | size_t allocate_limit; 29 | #endif 30 | } gc_profile_record; 31 | 32 | 33 | #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) 34 | #pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */ 35 | #endif 36 | 37 | typedef struct RVALUE { 38 | union { 39 | struct { 40 | VALUE flags; /* always 0 for freed obj */ 41 | struct RVALUE *next; 42 | } free; 43 | struct RBasic basic; 44 | struct RObject object; 45 | struct RClass klass; 46 | struct RFloat flonum; 47 | struct RString string; 48 | struct RArray array; 49 | struct RRegexp regexp; 50 | struct RHash hash; 51 | struct RData data; 52 | struct RTypedData typeddata; 53 | struct RStruct rstruct; 54 | struct RBignum bignum; 55 | struct RFile file; 56 | struct RNode node; 57 | struct RMatch match; 58 | struct RRational rational; 59 | struct RComplex complex; 60 | } as; 61 | #ifdef GC_DEBUG 62 | const char *file; 63 | int line; 64 | #endif 65 | } RVALUE; 66 | 67 | #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) 68 | #pragma pack(pop) 69 | #endif 70 | 71 | struct heaps_slot { 72 | void *membase; 73 | RVALUE *slot; 74 | size_t limit; 75 | uintptr_t *bits; 76 | RVALUE *freelist; 77 | struct heaps_slot *next; 78 | struct heaps_slot *prev; 79 | struct heaps_slot *free_next; 80 | }; 81 | 82 | struct heaps_header { 83 | struct heaps_slot *base; 84 | uintptr_t *bits; 85 | }; 86 | 87 | struct sorted_heaps_slot { 88 | RVALUE *start; 89 | RVALUE *end; 90 | struct heaps_slot *slot; 91 | }; 92 | 93 | struct heaps_free_bitmap { 94 | struct heaps_free_bitmap *next; 95 | }; 96 | 97 | struct gc_list { 98 | VALUE *varptr; 99 | struct gc_list *next; 100 | }; 101 | 102 | #define STACK_CHUNK_SIZE 500 103 | 104 | typedef struct stack_chunk { 105 | VALUE data[STACK_CHUNK_SIZE]; 106 | struct stack_chunk *next; 107 | } stack_chunk_t; 108 | 109 | typedef struct mark_stack { 110 | stack_chunk_t *chunk; 111 | stack_chunk_t *cache; 112 | size_t index; 113 | size_t limit; 114 | size_t cache_size; 115 | size_t unused_cache_size; 116 | } mark_stack_t; 117 | 118 | #ifndef CALC_EXACT_MALLOC_SIZE 119 | #define CALC_EXACT_MALLOC_SIZE 0 120 | #endif 121 | 122 | typedef struct rb_objspace { 123 | struct { 124 | size_t limit; 125 | size_t increase; 126 | #if CALC_EXACT_MALLOC_SIZE 127 | size_t allocated_size; 128 | size_t allocations; 129 | #endif 130 | } malloc_params; 131 | struct { 132 | size_t increment; 133 | struct heaps_slot *ptr; 134 | struct heaps_slot *sweep_slots; 135 | struct heaps_slot *free_slots; 136 | struct sorted_heaps_slot *sorted; 137 | size_t length; 138 | size_t used; 139 | struct heaps_free_bitmap *free_bitmap; 140 | RVALUE *range[2]; 141 | RVALUE *freed; 142 | size_t live_num; 143 | size_t free_num; 144 | size_t free_min; 145 | size_t final_num; 146 | size_t do_heap_free; 147 | } heap; 148 | struct { 149 | int dont_gc; 150 | int dont_lazy_sweep; 151 | int during_gc; 152 | rb_atomic_t finalizing; 153 | } flags; 154 | struct { 155 | st_table *table; 156 | RVALUE *deferred; 157 | } final; 158 | mark_stack_t mark_stack; 159 | struct { 160 | int run; 161 | gc_profile_record *record; 162 | size_t count; 163 | size_t size; 164 | double invoke_time; 165 | } profile; 166 | struct gc_list *global_list; 167 | size_t count; 168 | int gc_stress; 169 | 170 | struct mark_func_data_struct { 171 | void *data; 172 | void (*mark_func)(VALUE v, void *data); 173 | } *mark_func_data; 174 | } rb_objspace_t; 175 | 176 | #define malloc_limit objspace->malloc_params.limit 177 | #define malloc_increase objspace->malloc_params.increase 178 | #define heaps objspace->heap.ptr 179 | #define heaps_length objspace->heap.length 180 | #define heaps_used objspace->heap.used 181 | #define lomem objspace->heap.range[0] 182 | #define himem objspace->heap.range[1] 183 | #define heaps_inc objspace->heap.increment 184 | #define heaps_freed objspace->heap.freed 185 | #define dont_gc objspace->flags.dont_gc 186 | #define during_gc objspace->flags.during_gc 187 | #define finalizing objspace->flags.finalizing 188 | #define finalizer_table objspace->final.table 189 | #define deferred_final_list objspace->final.deferred 190 | #define global_List objspace->global_list 191 | #define ruby_gc_stress objspace->gc_stress 192 | #define initial_malloc_limit initial_params.initial_malloc_limit 193 | #define initial_heap_min_slots initial_params.initial_heap_min_slots 194 | #define initial_free_min initial_params.initial_free_min 195 | 196 | #define is_lazy_sweeping(objspace) ((objspace)->heap.sweep_slots != 0) 197 | 198 | #define nonspecial_obj_id(obj) (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG) 199 | 200 | #define RANY(o) ((RVALUE*)(o)) 201 | #define has_free_object (objspace->heap.free_slots && objspace->heap.free_slots->freelist) 202 | 203 | #define HEAP_HEADER(p) ((struct heaps_header *)(p)) 204 | #define GET_HEAP_HEADER(x) (HEAP_HEADER((uintptr_t)(x) & ~(HEAP_ALIGN_MASK))) 205 | #define GET_HEAP_SLOT(x) (GET_HEAP_HEADER(x)->base) 206 | #define GET_HEAP_BITMAP(x) (GET_HEAP_HEADER(x)->bits) 207 | #define NUM_IN_SLOT(p) (((uintptr_t)(p) & HEAP_ALIGN_MASK)/sizeof(RVALUE)) 208 | #define BITMAP_INDEX(p) (NUM_IN_SLOT(p) / (sizeof(uintptr_t) * CHAR_BIT)) 209 | #define BITMAP_OFFSET(p) (NUM_IN_SLOT(p) & ((sizeof(uintptr_t) * CHAR_BIT)-1)) 210 | #define MARKED_IN_BITMAP(bits, p) (bits[BITMAP_INDEX(p)] & ((uintptr_t)1 << BITMAP_OFFSET(p))) 211 | 212 | // 213 | #define RANY(o) ((RVALUE*)(o)) 214 | // 215 | 216 | static inline int 217 | is_pointer_to_heap(rb_objspace_t *objspace, void *ptr) 218 | { 219 | register RVALUE *p = RANY(ptr); 220 | register struct sorted_heaps_slot *heap; 221 | register size_t hi, lo, mid; 222 | 223 | if (p < lomem || p > himem) return FALSE; 224 | if ((VALUE)p % sizeof(RVALUE) != 0) return FALSE; 225 | 226 | /* check if p looks like a pointer using bsearch*/ 227 | lo = 0; 228 | hi = heaps_used; 229 | while (lo < hi) { 230 | mid = (lo + hi) / 2; 231 | heap = &objspace->heap.sorted[mid]; 232 | if (heap->start <= p) { 233 | if (p < heap->end) 234 | return TRUE; 235 | lo = mid + 1; 236 | } 237 | else { 238 | hi = mid; 239 | } 240 | } 241 | return FALSE; 242 | } 243 | 244 | //from count_objects: 245 | #define FOR_EACH_HEAP_SLOT(p) for (i = 0; i < heaps_used; i++) {\ 246 | RVALUE *p = objspace->heap.sorted[i].start, *pend = objspace->heap.sorted[i].end;\ 247 | if(!p) continue;\ 248 | for (; p < pend; p++) { 249 | #define FOR_EACH_HEAP_SLOT_END(total) } total += objspace->heap.sorted[i].slot->limit; } 250 | 251 | #define NODE_OPTBLOCK 1000000 //FIXME -------------------------------------------------------------------------------- /ext/heap_dump/specific/ruby-2.0.0_preview1/internal_typed_data.h: -------------------------------------------------------------------------------- 1 | //FIXME: autogen this from ruby (this copied from 1.9.3p194) 2 | 3 | // thread.c: 4 | struct thgroup { 5 | int enclosed; 6 | VALUE group; 7 | }; 8 | 9 | // enumerator.c: 10 | struct enumerator { 11 | VALUE obj; 12 | ID meth; 13 | VALUE args; 14 | VALUE fib; 15 | VALUE dst; 16 | VALUE lookahead; 17 | VALUE feedvalue; 18 | VALUE stop_exc; 19 | }; 20 | 21 | // 22 | struct generator { 23 | VALUE proc; 24 | }; 25 | 26 | struct yielder { 27 | VALUE proc; 28 | }; 29 | 30 | 31 | // proc.c: 32 | struct METHOD { 33 | VALUE recv; 34 | VALUE rclass; 35 | VALUE defined_class; //FIXME: dump this 36 | ID id; 37 | rb_method_entry_t *me; 38 | struct unlinked_method_entry_list_entry *ume; 39 | }; 40 | 41 | // 42 | #define METHOD_DEFINITIONP(m) (m->me ? m->me->def : NULL) 43 | 44 | //class.c: 45 | #define HAVE_RB_CLASS_TBL 1 46 | //For som reason this fails to link directly on 1.9.3 :( 47 | 48 | //HACK: 49 | #include 50 | 51 | inline st_table * rb_get_class_tbl(){ 52 | Dl_info info; 53 | void* image; 54 | if(!dladdr(rb_intern, &info) || !info.dli_fname){ 55 | return NULL; 56 | } 57 | image = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL); 58 | // printf("Image is %p, addr is %p (%p rel)\n", image, rb_intern, ((void*)rb_intern - image)); 59 | if(image) 60 | { 61 | void* tbl = dlsym(image, "_rb_class_tbl"); 62 | dlclose(image); 63 | if(tbl) 64 | return tbl; 65 | } 66 | 67 | //TODO: parse sym table and calculate address? 68 | 69 | return NULL; 70 | } 71 | 72 | #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current())) 73 | #define GET_THREAD() ruby_current_thread 74 | 75 | //FIXME: get global const for it: rb_define_global_const("RUBY_ENGINE", ruby_engine_name = MKSTR(engine)); 76 | #define ruby_engine_name Qnil 77 | 78 | #define ID_ALLOCATOR 0 79 | 80 | //vm_trace.c 81 | typedef enum { 82 | RUBY_HOOK_FLAG_SAFE = 0x01, 83 | RUBY_HOOK_FLAG_DELETED = 0x02, 84 | RUBY_HOOK_FLAG_RAW_ARG = 0x04 85 | } rb_hook_flag_t; 86 | typedef struct rb_event_hook_struct { 87 | rb_hook_flag_t hook_flags; 88 | rb_event_flag_t events; 89 | rb_event_hook_func_t func; 90 | VALUE data; 91 | struct rb_event_hook_struct *next; 92 | } rb_event_hook_t; 93 | 94 | //vm_backtrace.c 95 | inline static int 96 | calc_lineno(const rb_iseq_t *iseq, const VALUE *pc) 97 | { 98 | return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded); 99 | } 100 | 101 | int rb_vm_get_sourceline(const rb_control_frame_t * cfp){ 102 | int lineno = 0; 103 | const rb_iseq_t *iseq = cfp->iseq; 104 | 105 | if (RUBY_VM_NORMAL_ISEQ_P(iseq)) { 106 | lineno = calc_lineno(cfp->iseq, cfp->pc); 107 | } 108 | return lineno; 109 | } 110 | -------------------------------------------------------------------------------- /heap_dump.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/heap_dump/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Vasily Fedoseyev"] 6 | gem.email = ["vasilyfedoseyev@gmail.com"] 7 | gem.description = %q{Ruby 1.9 heap contents dumper} 8 | gem.summary = %q{Allows to dump heap to track reference leaks, including leaks in proc contexts and fibers} 9 | gem.homepage = "https://github.com/Vasfed/heap_dump" 10 | 11 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 12 | gem.files = `git ls-files`.split("\n") 13 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 14 | gem.name = "heap_dump" 15 | gem.require_paths = ["lib"] 16 | gem.version = HeapDump::VERSION 17 | 18 | gem.required_ruby_version = '>=1.9.2' 19 | #gem.platform = Gem::Platform::CURRENT # other than osx - maybe later 20 | 21 | gem.extensions = "ext/heap_dump/extconf.rb" 22 | 23 | gem.add_dependency "debugger-ruby_core_source" 24 | gem.add_dependency 'yajl-ruby', '1.1' 25 | gem.add_development_dependency "rake-compiler" 26 | end 27 | -------------------------------------------------------------------------------- /lib/heap_dump.rb: -------------------------------------------------------------------------------- 1 | require "heap_dump/version" 2 | 3 | require 'rbconfig' 4 | require "heap_dump.#{RbConfig::CONFIG['DLEXT']}" 5 | 6 | module HeapDump 7 | # Dumps ruby object space to file 8 | def self.dump filename='dump.json', gc_before_dump=true 9 | GC.start if gc_before_dump 10 | return dump_ext(filename) 11 | end 12 | 13 | # provides an object count - like ObjectSpace.count_objects, but also for user classes 14 | def self.count_objects namespaces_array=[], gc=false 15 | unless namespaces_array.is_a?(Array) && namespaces_array.all?{|v|v.respond_to? :to_s} 16 | if namespaces_array.respond_to? :to_s 17 | namespaces_array = [namespaces_array.to_s] 18 | else 19 | #TODO: actually, better way is to accept anything convertable, even module itself 20 | raise ArgumentError.new("namespaces_array must be a symbol/string or array of strings/symbols") 21 | end 22 | end 23 | prefixes_array = namespaces_array.map{|c| c.to_s} 24 | return count_objects_ext(prefixes_array, !!gc) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/heap_dump/version.rb: -------------------------------------------------------------------------------- 1 | module HeapDump 2 | VERSION = "0.0.34" 3 | end 4 | --------------------------------------------------------------------------------