[
  {
    "path": ".gitignore",
    "content": "*.o\n*.so\n*.bundle\nMakefile\n*.dSYM\n*.log\n*.gem\next/dst/*\next/src/yajl-1.0.9/*\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009-2015  Joe Damato, Aman Gupta, and Jake Douglas\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# NOTE\n\nThis project is no longer maintained. It does not work for any Ruby version\nabove Ruby 1.8.7.\n\n# memprof\n\n(c) Joe Damato\n@joedamato\nhttp://timetobleed.com\n\nMemprof is a Ruby level memory profiler that can help you find reference\nleaks in your application.\n\nMemprof can also do very lightweight function call tracing to figure out\nwhich system and library calls are happening in your code.\n\n# Installation\n\n    gem install memprof\n\n# API\n\n## Memprof.stats\n\n    Memprof.start\n    12.times{ \"abc\" }\n    Memprof.stats\n    Memprof.stop\n\nStart tracking file/line information for objects created after calling\n`Memprof.start`, and print out a summary of file:line/class pairs\ncreated.\n\n    12 file.rb:2:String\n\n*Note*: Call `Memprof.stats` again after `GC.start` to see which objects\nare cleaned up by the garbage collector:\n\n    Memprof.start\n    10.times{ $last_str = \"abc\" }\n\n    puts '=== Before GC'\n    Memprof.stats\n\n    puts '=== After GC'\n    GC.start\n    Memprof.stats\n\n    Memprof.stop\n\nAfter `GC.start`, only the very last instance of `\"abc\"` will still\nexist:\n\n    === Before GC\n         10 file.rb:2:String\n    === After GC\n          1 file.rb:2:String\n\n*Note*: Use `Memprof.stats(\"/path/to/file\")` to write results to a file.\n\n*Note*: Use `Memprof.stats!` to clear out tracking data after printing\nout results.\n\n## Memprof.track\n\nSimple wrapper for `Memprof.stats` that will start/stop memprof around a\ngiven block of ruby code.\n\n    Memprof.track{\n      100.times{ \"abc\" }\n      100.times{ 1.23 + 1 }\n      100.times{ Module.new }\n    }\n\nFor the block of ruby code, print out file:line/class pairs for\nruby objects created.\n\n    100  file.rb:2:String\n    100  file.rb:3:Float\n    100  file.rb:4:Module\n\n*Note*: You can call GC.start at the end of the block to print out only\nobjects that are 'leaking' (i.e. objects that still have inbound\nreferences).\n\n*Note*: Use `Memprof.track(\"/path/to/file\")` to write the results to a\nfile instead of stdout.\n\n## Memprof.dump\n\n    Memprof.dump{\n      \"hello\" + \"world\"\n    }\n\nDump out all objects created in a given ruby block as detailed json\nobjects.\n\n    {\n      \"_id\": \"0x15e5018\",\n\n      \"file\": \"file.rb\",\n      \"line\": 2,\n\n      \"type\": \"string\",\n      \"class_name\": \"String\",\n\n      \"length\": 10,\n      \"data\": \"helloworld\"\n    }\n\n*Note*: Use `Memprof.dump(\"/path/to/filename\")` to write the json output\nto a file, one per line.\n\n## Memprof.dump_all\n\n    Memprof.dump_all(\"myapp_heap.json\")\n\nDump out all live objects inside the Ruby VM to `myapp_heap.json`, one\nper line.\n\n### [memprof.com](http://memprof.com) heap visualizer\n\n    # load memprof before requiring rubygems, so objects created by\n    # rubygems itself are tracked by memprof too\n    require `gem which memprof/signal`.strip\n\n    require 'rubygems'\n    require 'myapp'\n\nInstalls a `URG` signal handler and starts tracking file/line\ninformation for newly created ruby objects. When the process receives\n`SIGURG`, it will fork and call `Memprof.dump_all` to write out the\nentire heap to a json file.\n\nUse the `memprof` command to send the signal and upload the heap to\n[memprof.com](http://memprof.com):\n\n    memprof --pid <PID> --name my_leaky_app --key <API_KEY>\n\n## Memprof.trace\n\n    require 'open-uri'\n    require 'mysql'\n    require 'memcached'\n\n    Memprof.trace{\n      10.times{ Module.new }\n      10.times{ GC.start }\n      10.times{ open('http://google.com/') }\n      10.times{ Mysql.connect.query(\"select 1+2\") }\n      10.times{ Memcached.new.get('memprof') }\n    }\n\nFor a given block of ruby code, count:\n\n - number of objects created per type\n - number of calls to and time spent in GC\n - number of calls to and time spent in connect/read/write/select\n - number of calls to and time spent in mysql queries\n - number of calls to and responses to memcached commands\n - number of calls to and bytes through malloc/realloc/free\n\nThe resulting json report looks like:\n\n    {\n      \"objects\": {\n        \"created\": 10,\n        \"types\": {\n          \"module\": 10,  # Module.new\n        }\n      },\n\n      \"gc\": {\n        \"calls\": 10,     # GC.start\n        \"time\": 0.17198\n      },\n\n      \"fd\": {\n        \"connect\": {\n          \"calls\": 10,   # open('http://google.com')\n          \"time\": 0.0110\n        }\n      },\n\n      \"mysql\": {\n        \"queries\": 10,   # Mysql.connect.query(\"select 1+2\")\n        \"time\": 0.0006\n      },\n\n      \"memcache\": {\n        \"get\": {\n          \"calls\": 10,   # Memcached.new.get('memprof')\n          \"responses\": {\n            \"notfound\": 10\n          }\n        }\n      }\n    }\n\n*Note*: To write json to a file instead, set `Memprof.trace_filename =\n\"/path/to/file.json\"`\n\n## Memprof.trace_request\n\n    Memprof.trace_request(env){ @app.call(env) }\n\nLike `Memprof.trace`, but assume an incoming Rack request and include\ninformation about the request itself.\n\n    {\n      \"start\" : 1272424769750716,\n      \"tracers\" : {\n        /* ... */\n      },\n      \"rails\" : {\n        \"controller\" : \"home\",\n        \"action\" : \"index\"\n      },\n      \"request\" : {\n        \"REQUEST_URI\" : \"/home\",\n        \"REQUEST_METHOD\" : \"GET\",\n        \"REMOTE_ADDR\" : \"127.0.0.1\",\n        \"QUERY_STRING\" : null\n      },\n      \"time\" : 1.3442\n    }\n\n# Middlewares\n\n## Memprof::Middleware\n\n    require 'memprof/middleware'\n    config.middlewares.use(Memprof::Middleware)\n\nWrap each request in a `Memprof.track` to print out all object\nlocation/type pairs created during that request.\n\n*Note*: It is preferable to run this in staging or production mode with\nRails applications, since development mode creates a lot of unnecessary\nobjects during each request.\n\n*Note*: To force a GC run before printing out a report, pass in\n`:force_gc => true` to the middleware.\n\n## Memprof::Tracer\n\n    require 'memprof/tracer'\n    config.middleware.insert(0, Memprof::Tracer)\n\nWrap each request in a `Memprof.trace_request` and write results to\n`/tmp/memprof_tracer-PID.json`\n\n## Memprof::Filter\n\nSimilar to `Memprof::Tracer`, but for legacy Rails 2.2 applications.\n\n    class ApplicationController < ActionController::Base\n      require 'memprof/tracer'\n      around_filter(Memprof::Filter)\n    end\n\n# Compatibility\n\nMemprof supports all 1.8.x (MRI and REE) VMs, as long as they are 64-bit\nand contain debugging symbols. For best results, use RVM to compile ruby\nand make sure you are on a 64-bit machine.\n\nThe following ruby builds are not supported:\n\n - Ruby on small/medium EC2 instances (32-bit machines)\n - OSX's default system ruby (no debugging symbols, fat 32/64-bit\n   binary)\n\n*Note*: Many linux distributions do not package debugging symbols by\ndefault. You can usually install these separately, for example using\n`apt-get install libruby1.8-dbg`\n\n## Coming soon\n\n - support for Ruby 1.9\n - support for i386/i686 ruby builds\n\n# Credits\n\n - Jake Douglas for the Mach-O Snow Leopard support\n - Aman Gupta for various bug fixes and other cleanup\n - Rob Benson for initial 1.9 support and cleanup\n - Paul Barry for force_gc support in `Memprof::Middleware`\n\n"
  },
  {
    "path": "Rakefile",
    "content": "task :spec do\n  Dir.chdir('ext') do\n    sh \"make clean\" rescue nil\n    sh \"ruby extconf.rb\"\n    sh \"make\"\n  end\n  sh \"ruby spec/memprof_spec.rb\"\nend\ntask :default => :spec\n\n# Should be used like:\n# rake --trace ci[1.8.7,shared]\n\ntask :ci, [:ruby_type, :lib_option] do |t, args|\n  ruby_type, lib_option = args[:ruby_type], args[:lib_option]\n  raise \"#{ruby_type} is not a supported ruby version\" unless [\"1.8.6\", \"1.8.7\", \"ree\"].include?(ruby_type)\n  raise \"#{lib_option} is not a supported \" unless [\"shared\", \"static\"].include?(lib_option)\n\n  lib_option = case lib_option\n  when \"static\"\n    \"--disable-shared\"\n  when \"shared\"\n    \"--enable-shared\"\n  end\n\n  sh \"/usr/bin/env bash -c \\\"\n  source ~/.rvm/scripts/rvm &&\n  rvm install #{ruby_type} --reconfigure -C #{lib_option} &&\n  rvm #{ruby_type} --symlink memprof &&\n  memprof_gem install bacon\\\"\"\n\n  Dir.chdir('ext') do\n    sh '/usr/bin/env bash -c \"make clean\"' rescue nil\n    sh \"~/.rvm/bin/memprof_ruby extconf.rb\"\n    sh '/usr/bin/env bash -c \"make\"'\n  end\n  sh \"~/.rvm/bin/memprof_ruby spec/memprof_spec.rb\"\nend\n\n"
  },
  {
    "path": "bin/memprof",
    "content": "#!/usr/bin/env ruby\n\nrequire 'rubygems'\nrequire 'optparse'\nrequire 'restclient'\nrequire 'term/ansicolor'\n\nMEMPROF_URL = \"https://memprof.com/upload\"\n\nMEMPROF_BANNER = <<-EOF\nMemprof Uploader\nhttp://www.memprof.com\n======================\n\nEOF\n\nclass MemprofUploader\n  include Term::ANSIColor\n\n  def initialize(args=ARGV)\n    puts MEMPROF_BANNER\n\n    @parser = OptionParser.new do |opts|\n      opts.banner = \"Usage:\"\n      opts.on(\"-p\", \"--pid <pid>\", Integer, \"PID of the process to dump       (required)\")      {|arg| @pid = arg }\n      opts.on(\"-n\", \"--name <name>\",        \"Name for your dump               (required)\")      {|arg| @name = arg }\n      opts.on(\"-k\", \"--key <key>\",          \"Memprof.com API key              (required)\")      {|arg| @key = arg }\n      opts.on(\"-d\", \"--[no-]delete\",        \"Delete dump file after uploading (default true)\")  {|arg| @delete_dump = arg }\n      opts.on(\"-s\", \"--seconds <seconds>\",\n                    Integer,                \"Seconds to wait for the dump     (default 300)\")   {|arg| @secs = arg }\n      opts.on(\"-t\", \"--[no-]test\",          \"Test run (don't actually upload) (default false)\") {|arg| @test = arg }\n      opts.on(\"-f\", \"--file <path>\",        \"Upload specific json dump        (optional)\")      {|arg| @file = arg }\n      opts.on(\"--put-my-data-on-the-internet\",  \"Confirm that you understand\\n\" +\n                                                \"memprof.com will show all your            \\n\".rjust(80) +\n                                                \"internal data on the internet    (required)\".rjust(80)) {|arg| @confirmed = true}\n      opts.on(\"--info\") do\n        require 'rbconfig'\n        puts RUBY_DESCRIPTION if defined? RUBY_DESCRIPTION\n        puts \"CFLAGS='#{Config::CONFIG[\"CFLAGS\"]}' ./configure #{Config::CONFIG[\"configure_args\"]}\"\n        bin = \"#{Config::CONFIG['bindir']}/#{Config::CONFIG['ruby_install_name']}\"\n        puts `file #{bin}`\n\n        if RUBY_PLATFORM =~ /darwin/\n          puts `otool -L #{bin}`\n        else\n          puts `ldd #{bin}`\n        end\n\n        puts\n        exit!\n      end\n    end\n\n    begin\n      @parser.parse!\n    rescue Exception => e\n      if e.kind_of?(SystemExit)\n        raise e\n      else\n        fail_with(e.message)\n      end\n    end\n\n    # Make this default to true if the user didn't pass any preference.\n    @delete_dump = true if @delete_dump.nil?\n\n    # Make this default to 60 if the user didn't pass the number of seconds.\n    @secs ||= 300\n\n    if @file\n      fail_with(\"File not found: #{@file}\") unless File.exists?(@file)\n    end\n\n    if @pid.nil? and @file.nil?\n      fail_with(\"Missing PID! (-p PID)\")\n    elsif @name.nil? || @name.empty?\n      fail_with(\"Missing name! (-n 'my application')\")\n    elsif @key.nil? || @key.empty?\n      fail_with(\"Missing API key! (-k KEY)\")\n    elsif !@confirmed\n      fail_with(\"\\nERROR: You MUST CONFIRM that you understand ALL YOUR CODE \"+\n                \"WILL BE PUBLICLY \\nAVAILABLE ON THE INTERNET by passing \" +\n                \"--put-my-data-on-the-internet\", true)\n    end\n  end\n\n  def run!\n    dump_filename = @file ? compress_file(@file, \"/tmp/#{File.basename @file}.gz\") : compress_file(get_dump_filename(@pid))\n\n    begin\n      upload_dump(dump_filename, @name, @key)\n    ensure\n      if @delete_dump\n        File.delete(dump_filename)\n        puts \"\\nDeleted dump file.\"\n      end\n    end\n\n    puts \"Finished!\"\n  end\n\n  private\n\n  def compress_file(filename, output=nil)\n    puts \"Compressing with gzip...\"\n    output ||= \"#{filename}.gz\"\n    `gzip -c '#{filename}' > '#{output}'`\n    output\n  end\n\n  def get_dump_filename(pid)\n    # Get a listing of files before we signal for the new dump\n    # We'll compare against this later to see which is the new file.\n    old_files = Dir.glob(\"/tmp/memprof-#{pid}-*\")\n    signal_process(pid)\n    timeout = 30\n    puts \"Waiting #{timeout} seconds for process #{pid} to create a new dump...\"\n\n    file = nil\n\n    timeout.times do |i|\n      sleep 1 unless file = (Dir.glob(\"/tmp/memprof-#{pid}-*\") - old_files).first and break\n      fail_with(\"Timed out after waiting #{timeout} seconds. Make sure you added require '#{File.expand_path('../../lib/memprof/signal', __FILE__)}' to your application.\") if i+1 == timeout\n    end\n\n    puts \"\\nFound file #{file}\"\n\n    if file =~ /\\.IN_PROGRESS/\n      file = file.sub(/\\.IN_PROGRESS/, \"\")\n      puts \"Dump in progress. Waiting #{@secs} seconds for it to complete...\"\n      @secs.times do |i|\n        sleep 1 unless File.exist?(file) and break\n        fail_with(\"Timed out after waiting #{@secs} seconds\") if i+1 == @secs\n      end\n    end\n\n    file\n  end\n\n  def upload_dump(filename, name, key)\n    return if @test\n\n    file = File.open(filename, \"r\")\n\n    puts \"\\nUploading to memprof.com...\"\n    response = RestClient.post(MEMPROF_URL, :upload => file, :name => name, :key => key)\n\n    puts \"Response from server:\"\n    p response.to_s\n  end\n\n  def fail_with(str, yell=true)\n    puts @parser.to_s\n    puts\n    if yell\n      print red, bold, str, reset\n      puts\n    else\n      puts \"\\n\" + str\n    end\n    puts\n    exit(1)\n  end\n\n  def signal_process(pid)\n    begin\n      Process.kill(\"URG\", pid)\n    rescue Errno::ESRCH\n      fail_with(\"No such process #{pid}!\")\n    end\n    puts \"Signaled process #{pid} with SIGURG\"\n  end\n\nend\n\nMemprofUploader.new(ARGV).run!"
  },
  {
    "path": "ext/arch.h",
    "content": "#if !defined (_ARCH_H_)\n#define _ARCH_H_\n\n/*\n * The only supported architectures at this time are i{3-6}86 and amd64. All\n * other architectures should fail.\n */\n#if defined(_ARCH_i386_) || defined(_ARCH_i686_)\n#include \"i386.h\"\n#elif defined(_ARCH_x86_64_)\n#include \"x86_64.h\"\n#else\n#error \"Unsupported architecture! Cannot continue compilation.\"\n#endif\n\n#include \"x86_gen.h\"\n\n/*\n * arch_get_st2_tramp - architecture specific stage 2 trampoline getter\n *\n * This function will return a pointer to an area of memory that contains\n * the stage 2 trampoline for this architecture.\n *\n * This function will also set the out parameter size equal to the size of this\n * region, if size is not NULL.\n */\nvoid *\narch_get_st2_tramp(size_t *size);\n\n/*\n * arch_insert_st1_tramp - architecture specific stage 1 trampoline insert\n *\n * Given:\n *  start - a start address to attempt to insert a trampoline at\n *  trampee - the address of the function to trampoline\n *  tramp - a pointer to the trampoline\n *\n * This function will inspect the start address to determine if an instruction\n * which transfers execution to the trampee is present. If so, this function\n * will rewrite the instruction to ensure that tramp is called instead.\n *\n * Returns 0 on success, 1 otherwise.\n */\nint\narch_insert_st1_tramp(void *start, void *trampee, void *tramp);\n\n/*\n * arch_get_inline_st2_tramp - architecture specific inline stage 2 tramp getter\n *\n * This function will return a pointer to an area of memory that contains the\n * stage 2 inline trampoline for this architecture.\n *\n * This function will also set the out parameter size equal to the size of this\n * region, if size is not NULL.\n */\nvoid *\narch_get_inline_st2_tramp(size_t *size);\n\n/*\n * Given:\n *    - addr - The base address of an instruction sequence.\n *\n *    - marker - This is the marker to search for which will indicate that the\n *      instruction sequence has been located.\n *\n *    - trampoline - The address of the handler to redirect execution to.\n *\n *    - table_entry - Address of where the stage 2 trampoline code will reside\n *\n * This function will:\n *    Insert and setup the stage 1 and stage 2 trampolines if addr points to an\n *    instruction that could be from the inlined add_freelist function.\n *\n * This function returns 1 on failure and 0 on success.\n */\nint\narch_insert_inline_st2_tramp(void *addr, void *marker, void *trampoline, void *table_entry);\n#endif\n"
  },
  {
    "path": "ext/bin_api.h",
    "content": "#if !defined(BIN_API__)\n#define BIN_API__\n#include <stddef.h>\n#include <stdint.h>\n\n/*\n * Some useful foward declarations for function protoypes here and elsewhere.\n */\nstruct tramp_st2_entry;\nstruct inline_tramp_st2_entry;\n\n/*\n * bin_init - Initialize the binary format specific layer.\n */\nvoid\nbin_init();\n\n/*\n * bin_find_symbol - find a symbol\n *\n * Given:\n *  - sym - a symbol name\n *  - size - optional out parameter\n *  - search_libs - 0 to search only ruby/libruby, 1 to search other\n *    libraries, too.\n *\n * This function will search for the symbol sym and return its address if\n * found, or NULL if the symbol could not be found.\n *\n * Optionally, this function will set its out parameter size equal to the\n * size of the symbol.\n */\nvoid *\nbin_find_symbol(const char *sym, size_t *size, int search_libs);\n\n/*\n * bin_find_symbol_name - find a symbol's name\n *\n * Given:\n *  - sym - a symbol address\n *\n * This function will search for the symbol sym and return its name if\n * found, or NULL if the symbol could not be found.\n */\nconst char *\nbin_find_symbol_name(void *sym);\n\n/*\n * bin_allocate_page - allocate a page suitable for trampolines\n *\n * This function will allocate a page of memory in the right area of the\n * virtual address space and with the appropriate permissions for stage 2\n * trampoline code to live and execute.\n */\nvoid *\nbin_allocate_page(void);\n\n/*\n * bin_type_size - Return size (in bytes) of a given type.\n *\n * Given:\n *  - type - a string representation of a type\n *\n * This function will return the size (in bytes) of the type. If no such type\n * can be found, return 0.\n */\nsize_t\nbin_type_size(const char *type);\n\n/*\n * bin_type_member_offset - Return the offset (in bytes) of member in type.\n *\n * Given:\n *  - type - a string representation of a type\n *  - member - a string representation of a field int he type\n *\n * This function will return the offset (in bytes) of member in type.\n *\n * On failure, this function returns -1.\n */\nint\nbin_type_member_offset(const char *type, const char *member);\n\n/*\n * bin_update_image - Update a binary image in memory\n *\n * Given:\n *  - trampee - the name of the symbol to hook\n *  - tramp - the stage 2 trampoline entry\n *  - orig_func - out parameter storing the address of the function that was\n *    originally being called.\n *\n * this function will update the binary image so that all calls to trampee will\n * be routed to tramp.\n *\n * Returns 0 on success.\n */\nint\nbin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig_func);\n#endif\n"
  },
  {
    "path": "ext/elf.c",
    "content": "#if defined(HAVE_ELF)\n#define _GNU_SOURCE\n#include \"bin_api.h\"\n#include \"arch.h\"\n#include \"util.h\"\n\n#include <assert.h>\n#include <dwarf.h>\n#include <err.h>\n#include <error.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <libdwarf.h>\n#include <libelf/gelf.h>\n#include <libgen.h>\n#include <limits.h>\n#include <link.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <sysexits.h>\n#include <unistd.h>\n\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\nextern struct memprof_config memprof_config;\n\n/* The size of a PLT entry */\n#define PLT_ENTRY_SZ  (16)\n\n/* The system-wide debug symbol directory */\n/* XXX this should be set via extconf and not hardcoded */\n#define DEBUGDIR   \"/usr/lib/debug\"\n\n/* Keep track of whether this ruby is built with a shared library or not */\nstatic int libruby = 0;\n\nstatic Dwarf_Debug dwrf = NULL;\n\n/* Set of ELF specific state about this Ruby binary/shared object */\nstatic struct elf_info *ruby_info = NULL;\n\nstruct elf_info {\n  int fd;\n  Elf *elf;\n\n  GElf_Addr base_addr;\n\n  void *text_segment;\n  size_t text_segment_len;\n\n  GElf_Addr got_addr;\n\n  GElf_Addr relplt_addr;\n  Elf_Data *relplt;\n  size_t relplt_count;\n\n  GElf_Addr plt_addr;\n  Elf_Data *plt;\n  size_t plt_size;\n  size_t plt_count;\n\n  Elf_Data *debuglink_data;\n\n  GElf_Ehdr ehdr;\n\n  Elf_Data *dynsym;\n  size_t dynsym_count;\n  const char *dynstr;\n\n  GElf_Shdr symtab_shdr;\n  Elf_Data *symtab_data;\n\n  const char *filename;\n\n  struct elf_info *debug_data;\n};\n\n/* These callback return statuses are used to tell the invoker of the callback\n * (walk_linkmap) whether to continue walking or bail out.\n */\ntypedef enum {\n  CB_CONTINUE,\n  CB_EXIT,\n} linkmap_cb_status;\n\n/* A callback type that specifies a function that can be fired on each link map\n * entry.\n */\ntypedef linkmap_cb_status (*linkmap_cb)(struct link_map *, void *);\ntypedef void (*linkmap_lib_cb)(struct elf_info *lib, void *data);\n\nstatic void open_elf(struct elf_info *info);\nstatic int dissect_elf(struct elf_info *info, int find_debug);\nstatic void *find_plt_addr(const char *symname, struct elf_info *info);\nstatic void walk_linkmap(linkmap_cb cb, void *data);\n\n/*\n * plt_entry - procedure linkage table entry\n *\n * This struct is intended to be \"laid onto\" a piece of memory to ease the\n * parsing, use, modification, and length calculation of PLT entries.\n *\n * For example:\n *    jmpq  *0xaaf00d(%rip)   # jump to GOT entry\n *\n *    # the following instructions are only hit if function has not been\n *    # resolved previously.\n *\n *    pushq  $0x4e            # push ID\n *    jmpq  0xcafebabefeedface # invoke the link linker\n */\nstruct plt_entry {\n    unsigned char jmp[2];\n    int32_t jmp_disp;\n\n    /* There is no reason (currently) to represent the pushq and jmpq\n     * instructions which invoke the linker.\n     *\n     * We don't need those to hook the GOT; we only need the jmp_disp above.\n     *\n     * TODO represent the extra instructions\n     */\n    unsigned char pad[10];\n} __attribute__((__packed__));\n\n/*\n * get_got_addr - given a PLT entry, return the global offset table entry that\n * the entry uses.\n */\nstatic void *\nget_got_addr(struct plt_entry *plt, struct elf_info *info)\n{\n  void *addr = NULL;\n\n  assert(plt != NULL);\n  assert(plt->jmp[0] == 0xff);\n\n  if (plt->jmp[1] == 0x25) {\n#if defined(_ARCH_x86_64_)\n    // jmpq   *0x2ccf3a(%rip)\n    addr = (void *)&(plt->pad) + plt->jmp_disp;\n#else\n    // jmp    *0x81060f0\n    addr = (void *)(plt->jmp_disp);\n#endif\n  } else if (plt->jmp[1] == 0xa3) {\n    // jmp    *0x130(%ebx)\n    addr = (void *)(info->base_addr + info->got_addr + plt->jmp_disp);\n  }\n\n  dbg_printf(\"PLT addr: %p, .got.plt slot: %p\\n\", plt, addr);\n  return addr;\n}\n\n/*\n * overwrite_got - given the address of a PLT entry, overwrite the address\n * in the GOT that the PLT entry uses with the address in tramp.\n *\n * returns the original function address\n */\nstatic void *\noverwrite_got(void *plt, const void *tramp, struct elf_info *info)\n{\n  assert(plt != NULL);\n  assert(tramp != NULL);\n  void *ret = NULL;\n\n  memcpy(&ret, get_got_addr(plt, info), sizeof(void *));\n  copy_instructions(get_got_addr(plt, info), &tramp, sizeof(void *));\n  dbg_printf(\"GOT value overwritten to: %p, from: %p\\n\", tramp, ret);\n  return ret;\n}\n\nstruct plt_hook_data {\n  const char *sym;\n  void *addr;\n};\n\nstruct dso_iter_data {\n  linkmap_lib_cb cb;\n  void *passthru;\n};\n\nstatic linkmap_cb_status\nfor_each_dso_cb(struct link_map *map, void *data)\n{\n  struct dso_iter_data *iter_data = data;\n  struct elf_info curr_lib;\n\n  /* skip a few things we don't care about */\n  if (strstr(map->l_name, \"linux-vdso\")) {\n    dbg_printf(\"found vdso (skipping): %s\\n\", map->l_name);\n    return CB_CONTINUE;\n  } else if (strstr(map->l_name, \"ld-linux\")) {\n    dbg_printf(\"found ld-linux (skipping): %s\\n\", map->l_name);\n    return CB_CONTINUE;\n  } else if (strstr(map->l_name, \"libruby\")) {\n    dbg_printf(\"found libruby (skipping): %s\\n\", map->l_name);\n    return CB_CONTINUE;\n  } else if (strstr(map->l_name, \"memprof\")) {\n    dbg_printf(\"found memprof (skipping): %s\\n\", map->l_name);\n    return CB_CONTINUE;\n  } else if (!map->l_name || map->l_name[0] == '\\0') {\n    dbg_printf(\"found an empty string (skipping)\\n\");\n    return CB_CONTINUE;\n  }\n  memset(&curr_lib, 0, sizeof(curr_lib));\n  dbg_printf(\"trying to open elf object: %s\\n\", map->l_name);\n  curr_lib.filename = map->l_name;\n  open_elf(&curr_lib);\n\n  if (curr_lib.elf == NULL) {\n    dbg_printf(\"opening the elf object (%s) failed! (skipping)\\n\", map->l_name);\n    return CB_CONTINUE;\n  }\n\n  curr_lib.base_addr = map->l_addr;\n  curr_lib.filename = map->l_name;\n\n  if (dissect_elf(&curr_lib, 0) == 2) {\n    dbg_printf(\"elf file, %s hit an unrecoverable error (skipping)\\n\", map->l_name);\n    elf_end(curr_lib.elf);\n    close(curr_lib.fd);\n    return CB_CONTINUE;\n  }\n\n  dbg_printf(\"dissected the elf file: %s, base: %lx\\n\",\n      curr_lib.filename, (unsigned long)curr_lib.base_addr);\n\n  iter_data->cb(&curr_lib, iter_data->passthru);\n\n  elf_end(curr_lib.elf);\n  close(curr_lib.fd);\n  return CB_CONTINUE;\n}\n\nstatic void\nfor_each_dso(linkmap_lib_cb cb, void *passthru)\n{\n  struct dso_iter_data data;\n  data.cb = cb;\n  data.passthru = passthru;\n  walk_linkmap(for_each_dso_cb, &data);\n}\n\nstatic void\nhook_required_objects(struct elf_info *info, void *data)\n{\n  assert(info != NULL);\n  struct plt_hook_data *hook_data = data;\n  void *trampee_addr = NULL;\n\n  if ((trampee_addr = find_plt_addr(hook_data->sym, info)) != NULL) {\n    dbg_printf(\"found: %s @ %p\\n\", hook_data->sym, trampee_addr);\n    overwrite_got(trampee_addr, hook_data->addr, info);\n  }\n\n  return;\n}\n\n/*\n * do_bin_allocate_page - internal page allocation routine\n *\n * This function allocates a page suitable for stage 2 trampolines. This page\n * is allocated based on the location of the text segment of the Ruby binary\n * or libruby.\n *\n * The page has to be located in a 32bit window from the Ruby code so that\n * jump and call instructions can redirect execution there.\n *\n * This function returns the address of the page found or NULL if no page was\n * found.\n */\nstatic void *\ndo_bin_allocate_page(struct elf_info *info)\n{\n  void * ret = NULL, *addr = NULL;\n  uint32_t count = 0;\n\n  if (!info)\n    return NULL;\n\n  if (libruby) {\n    /* There is a libruby. Start at the end of the text segment and search for\n     * a page.\n     */\n    addr = info->text_segment + info->text_segment_len;\n    for (; count < USHRT_MAX; addr += memprof_config.pagesize, count += memprof_config.pagesize) {\n      ret = mmap(addr, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);\n      if (ret != MAP_FAILED) {\n        memset(ret, 0x90, memprof_config.pagesize);\n        return ret;\n      }\n    }\n  } else {\n    /* if there is no libruby, use the linux specific MAP_32BIT flag which will\n     * grab a page in the lower 4gb of the address space.\n     */\n    assert((size_t)info->text_segment <= UINT_MAX);\n#ifndef MAP_32BIT\n#define MAP_32BIT 0 // no MAP_32BIT defined on certain 32bit systems\n#endif\n    return mmap(NULL, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);\n  }\n\n  return NULL;\n}\n\n/*\n * bin_allocate_page - allocate a page suitable for holding stage 2 trampolines\n *\n * This function is just a wrapper which passes through some internal state.\n */\nvoid *\nbin_allocate_page()\n{\n  return do_bin_allocate_page(ruby_info);\n}\n\n/*\n * get_plt_addr - architecture specific PLT entry retrieval\n *\n * Given the internal data and an index, this function returns the address of\n * the PLT entry at that address.\n *\n * A PLT entry takes the form:\n *\n * jmpq *0xfeedface(%rip)\n * pushq $0xaa\n * jmpq 17110\n */\nstatic inline GElf_Addr\nget_plt_addr(struct elf_info *info, size_t ndx) {\n  assert(info != NULL);\n  dbg_printf(\"file: %s, base: %lx\\n\", info->filename, (unsigned long)info->base_addr);\n  return info->base_addr + info->plt_addr + (ndx + 1) * PLT_ENTRY_SZ;\n}\n\n/*\n * find_got_addr - find the global offset table entry for specific symbol name.\n *\n * Given:\n *  - syname - the symbol name\n *  - info   - internal information about the ELF object to search\n *\n * This function searches the .rela.plt section of an ELF binary, searcing for\n * entries that match the symbol name passed in. If one is found, the address\n * of corresponding entry in .plt is returned.\n */\nstatic void *\nfind_plt_addr(const char *symname, struct elf_info *info)\n{\n  assert(symname != NULL);\n\n  size_t i = 0;\n\n  if (info == NULL) {\n    info = ruby_info;\n  }\n\n  assert(info != NULL);\n\n  /* Search through each of the .rela.plt entries */\n  for (i = 0; i < info->relplt_count; i++) {\n    GElf_Rel rel;\n    GElf_Rela rela;\n    GElf_Sym sym;\n    GElf_Addr addr;\n    void *ret = NULL;\n    const char *name;\n\n    if (info->relplt->d_type == ELF_T_RELA) {\n      ret = gelf_getrela(info->relplt, i, &rela);\n      if (ret == NULL\n          || ELF64_R_SYM(rela.r_info) >= info->dynsym_count\n          || gelf_getsym(info->dynsym, ELF64_R_SYM(rela.r_info), &sym) == NULL)\n        continue;\n\n    } else if (info->relplt->d_type == ELF_T_REL) {\n      ret = gelf_getrel(info->relplt, i, &rel);\n      if (ret == NULL\n          || ELF64_R_SYM(rel.r_info) >= info->dynsym_count\n          || gelf_getsym(info->dynsym, ELF64_R_SYM(rel.r_info), &sym) == NULL)\n        continue;\n    } else {\n      dbg_printf(\"unknown relplt entry type: %d\\n\", info->relplt->d_type);\n      continue;\n    }\n\n    name = info->dynstr + sym.st_name;\n\n    /* The name matches the name of the symbol passed in, so get the PLT entry\n     * address and return it.\n     */\n    if (strcmp(symname, name) == 0) {\n      addr = get_plt_addr(info, i);\n      return (void *)addr;\n    }\n  }\n\n  return NULL;\n}\n\n/*\n * do_bin_find_symbol - internal symbol lookup function.\n *\n * Given:\n *  - sym - the symbol name to look up\n *  - size - an optional out argument holding the size of the symbol\n *  - elf - an elf information structure\n *\n * This function will return the address of the symbol (setting size if desired)\n * or NULL if nothing can be found.\n */\nstatic void *\ndo_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)\n{\n  const char *name = NULL;\n\n  assert(sym != NULL);\n  assert(elf != NULL);\n\n  ElfW(Sym) *esym, *lastsym = NULL;\n  if (elf->symtab_data && elf->symtab_data->d_buf) {\n    dbg_printf(\"checking symtab....\\n\");\n    esym = (ElfW(Sym)*) elf->symtab_data->d_buf;\n    lastsym = (ElfW(Sym)*) ((char*) elf->symtab_data->d_buf + elf->symtab_data->d_size);\n\n    assert(esym <= lastsym);\n\n    for (; esym < lastsym; esym++){\n      /* ignore numeric/empty symbols */\n      if ((esym->st_value == 0) ||\n          (ELF32_ST_BIND(esym->st_info)== STB_NUM))\n        continue;\n\n      name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);\n\n      if (name && strcmp(name, sym) == 0) {\n        if (size) {\n          *size = esym->st_size;\n        }\n        dbg_printf(\"Found symbol: %s in symtab\\n\", sym);\n        return elf->base_addr + (void *)esym->st_value;\n      }\n    }\n  }\n\n  if (elf->dynsym && elf->dynsym->d_buf) {\n    dbg_printf(\"checking dynsymtab....\\n\");\n    esym = (ElfW(Sym) *) elf->dynsym->d_buf;\n    lastsym = (ElfW(Sym) *) ((char *) elf->dynsym->d_buf + elf->dynsym->d_size);\n\n    for (; esym < lastsym; esym++){\n      /* ignore numeric/empty symbols */\n      if ((esym->st_value == 0) ||\n          (elf->dynstr == 0) ||\n          (ELF32_ST_BIND(esym->st_info)== STB_NUM))\n        continue;\n\n      name = elf->dynstr + esym->st_name;\n\n      if (name && strcmp(name, sym) == 0) {\n        if (size) {\n          *size = esym->st_size;\n        }\n        dbg_printf(\"Found symbol: %s in dynsym\\n\", sym);\n        return elf->base_addr + (void *)esym->st_value;\n      }\n    }\n  }\n\n  dbg_printf(\"Couldn't find symbol: %s in dynsym\\n\", sym);\n  return NULL;\n}\n\nstatic void\nfind_symbol_cb(struct elf_info *info, void *data)\n{\n  assert(info != NULL);\n  void *trampee_addr = NULL;\n  struct plt_hook_data *hook_data = data;\n  void *ret = do_bin_find_symbol(hook_data->sym, NULL, info);\n  if (ret) {\n    hook_data->addr = ret;\n    dbg_printf(\"found %s @ %p, fn addr: %p\\n\", hook_data->sym, trampee_addr,\n        hook_data->addr);\n  }\n}\n\n/*\n * bin_find_symbol - find the address of a given symbol and set its size if\n * desired.\n *\n * This function is just a wrapper for the internal symbol lookup function.\n */\nvoid *\nbin_find_symbol(const char *sym, size_t *size, int search_libs)\n{\n  void *ret = do_bin_find_symbol(sym, size, ruby_info);\n\n  if (!ret && search_libs) {\n    dbg_printf(\"Didn't find symbol: %s in ruby, searching other libs\\n\", sym);\n    struct plt_hook_data hd;\n    hd.sym = sym;\n    hd.addr = NULL;\n    for_each_dso(find_symbol_cb, &hd);\n    ret = hd.addr;\n  }\n\n  return ret;\n}\n\n/*\n * do_bin_find_symbol_name - internal symbol name lookup function.\n *\n * Given:\n *  - sym - the symbol address to look up\n *  - elf - an elf information structure\n *\n * This function will return the name of the symbol\n * or NULL if nothing can be found.\n */\nstatic const char *\ndo_bin_find_symbol_name(void *sym, struct elf_info *elf)\n{\n  char *name = NULL;\n  void *ptr;\n\n  assert(sym != NULL);\n  assert(elf != NULL);\n\n  assert(elf->symtab_data != NULL);\n  assert(elf->symtab_data->d_buf != NULL);\n\n  ElfW(Sym) *esym = (ElfW(Sym)*) elf->symtab_data->d_buf;\n  ElfW(Sym) *lastsym = (ElfW(Sym)*) ((char*) elf->symtab_data->d_buf + elf->symtab_data->d_size);\n\n  assert(esym <= lastsym);\n\n  for (; esym < lastsym; esym++){\n    /* ignore weak/numeric/empty symbols */\n    if ((esym->st_value == 0) ||\n        (ELF32_ST_BIND(esym->st_info)== STB_WEAK) ||\n        (ELF32_ST_BIND(esym->st_info)== STB_NUM))\n      continue;\n\n    ptr = elf->base_addr + (void *)esym->st_value;\n    name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);\n\n    if (ptr == sym)\n      return name;\n  }\n\n  return NULL;\n}\n\n/*\n * Do the same thing as in bin_find_symbol above, but compare addresses and return the string name.\n */\nconst char *\nbin_find_symbol_name(void *sym) {\n  return do_bin_find_symbol_name(sym, ruby_info);\n}\n\n/*\n * bin_update_image - update the ruby binary image in memory.\n *\n * Given -\n *  trampee - the name of the symbol to hook\n *  tramp - the stage 2 trampoline entry\n *  orig_func - out parameter storing the address of the function that was\n *  originally called.\n *\n * This function will update the ruby binary image so that all calls to trampee\n * will be routed to tramp.\n *\n * Returns 0 on success\n */\nint\nbin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig_func)\n{\n  void *trampee_addr = NULL;\n\n  assert(trampee != NULL);\n  assert(tramp != NULL);\n  assert(tramp->addr != NULL);\n\n  /* first check if the symbol is in the PLT */\n  trampee_addr = find_plt_addr(trampee, NULL);\n\n  if (trampee_addr) {\n    void *ret = NULL;\n    dbg_printf(\"Found %s in the PLT, inserting tramp...\\n\", trampee);\n    ret = overwrite_got(trampee_addr, tramp->addr, ruby_info);\n\n    assert(ret != NULL);\n\n    if (orig_func) {\n      *orig_func = ret;\n      dbg_printf(\"setting orig function: %p\\n\", *orig_func);\n    }\n  } else {\n    trampee_addr = bin_find_symbol(trampee, NULL, 0);\n    dbg_printf(\"Couldn't find %s in the PLT...\\n\", trampee);\n\n    if (trampee_addr) {\n      unsigned char *byte = ruby_info->text_segment;\n      size_t count = 0;\n      int num = 0;\n\n      assert(byte != NULL);\n\n      if (orig_func) {\n        *orig_func = trampee_addr;\n      }\n\n      for(; count < ruby_info->text_segment_len; byte++, count++) {\n        if (arch_insert_st1_tramp(byte, trampee_addr, tramp) == 0) {\n          num++;\n        }\n      }\n\n      dbg_printf(\"Inserted %d tramps for: %s\\n\", num, trampee);\n    }\n  }\n\n  dbg_printf(\"Trying to hook %s in other libraries...\\n\", trampee);\n\n  struct plt_hook_data data;\n  data.addr = tramp->addr;\n  data.sym = trampee;\n  for_each_dso(hook_required_objects, &data);\n\n  dbg_printf(\"Done searching other libraries for %s\\n\", trampee);\n\n  return 0;\n}\n\n\nstatic Dwarf_Die\ncheck_die(Dwarf_Die die, const char *search, Dwarf_Half type)\n{\n  char *name = 0;\n  Dwarf_Error error = 0;\n  Dwarf_Half tag = 0;\n  int ret = 0;\n  int res = dwarf_diename(die,&name,&error);\n  if (res == DW_DLV_ERROR) {\n    printf(\"Error in dwarf_diename\\n\");\n    exit(1);\n  }\n  if (res == DW_DLV_NO_ENTRY) {\n    return 0;\n  }\n\n  res = dwarf_tag(die,&tag,&error);\n  if (res != DW_DLV_OK) {\n    printf(\"Error in dwarf_tag\\n\");\n    exit(1);\n  }\n\n  if (tag == type && strcmp(name, search) == 0){\n    //printf(\"tag: %d name: '%s' die: %p\\n\",tag,name,die);\n    ret = 1;\n  }\n\n  dwarf_dealloc(dwrf,name,DW_DLA_STRING);\n\n  return ret ? die : 0;\n}\n\nstatic Dwarf_Die\nsearch_dies(Dwarf_Die die, const char *name, Dwarf_Half type)\n{\n  int res = DW_DLV_ERROR;\n  Dwarf_Die cur_die=die;\n  Dwarf_Die child = 0;\n  Dwarf_Error error;\n  Dwarf_Die ret = 0;\n\n  ret = check_die(cur_die, name, type);\n  if (ret)\n    return ret;\n\n  for(;;) {\n    Dwarf_Die sib_die = 0;\n    res = dwarf_child(cur_die,&child,&error);\n    if (res == DW_DLV_ERROR) {\n      printf(\"Error in dwarf_child\\n\");\n      exit(1);\n    }\n    if (res == DW_DLV_OK) {\n      ret = search_dies(child,name,type);\n      if (ret) {\n        if (cur_die != die && cur_die != ret)\n          dwarf_dealloc(dwrf,cur_die,DW_DLA_DIE);\n        return ret;\n      }\n    }\n    /* res == DW_DLV_NO_ENTRY */\n\n    res = dwarf_siblingof(dwrf,cur_die,&sib_die,&error);\n    if (res == DW_DLV_ERROR) {\n      printf(\"Error in dwarf_siblingof\\n\");\n      exit(1);\n    }\n    if (res == DW_DLV_NO_ENTRY) {\n      /* Done at this level. */\n      break;\n    }\n    /* res == DW_DLV_OK */\n\n    if (cur_die != die)\n      dwarf_dealloc(dwrf,cur_die,DW_DLA_DIE);\n\n    cur_die = sib_die;\n    ret = check_die(cur_die, name, type);\n    if (ret)\n      return ret;\n  }\n  return 0;\n}\n\nstatic Dwarf_Die\nfind_die(const char *name, Dwarf_Half type)\n{\n  Dwarf_Die ret = 0;\n  Dwarf_Unsigned cu_header_length = 0;\n  Dwarf_Half version_stamp = 0;\n  Dwarf_Unsigned abbrev_offset = 0;\n  Dwarf_Half address_size = 0;\n  Dwarf_Unsigned next_cu_header = 0;\n  Dwarf_Error error;\n  int cu_number = 0;\n\n  Dwarf_Die no_die = 0;\n  Dwarf_Die cu_die = 0;\n  int res = DW_DLV_ERROR;\n\n  for (;;++cu_number) {\n    no_die = 0;\n    cu_die = 0;\n    res = DW_DLV_ERROR;\n\n    res = dwarf_next_cu_header(dwrf, &cu_header_length, &version_stamp, &abbrev_offset, &address_size, &next_cu_header, &error);\n\n    if (res == DW_DLV_ERROR) {\n      printf(\"Error in dwarf_next_cu_header\\n\");\n      exit(1);\n    }\n    if (res == DW_DLV_NO_ENTRY) {\n      /* Done. */\n      return 0;\n    }\n\n    /* The CU will have a single sibling, a cu_die. */\n    res = dwarf_siblingof(dwrf,no_die,&cu_die,&error);\n\n    if (res == DW_DLV_ERROR) {\n      printf(\"Error in dwarf_siblingof on CU die \\n\");\n      exit(1);\n    }\n    if (res == DW_DLV_NO_ENTRY) {\n      /* Impossible case. */\n      printf(\"no entry! in dwarf_siblingof on CU die \\n\");\n      exit(1);\n    }\n\n    ret = search_dies(cu_die,name,type);\n\n    if (cu_die != ret)\n      dwarf_dealloc(dwrf,cu_die,DW_DLA_DIE);\n\n    if (ret)\n      break;\n  }\n\n  /* traverse to the end to reset */\n  while ((dwarf_next_cu_header(dwrf, &cu_header_length, &version_stamp, &abbrev_offset, &address_size, &next_cu_header, &error)) != DW_DLV_NO_ENTRY);\n\n  return ret ? ret : 0;\n}\n\n/*\n * find_libruby_cb - a callback which attempts to locate libruby in the linkmap\n *\n * This callback is fired from walk_linkmap for each item on the linkmap. This\n * function searchs for libruby and stores data about it in the object passed\n * through.\n *\n * This function return CB_EXIT once libruby is found to terminate the link map\n * walker. If libruby isn't found, this function returns CB_CONTINUE.\n */\nstatic linkmap_cb_status\nfind_libruby_cb(struct link_map *map, void *data)\n{\n  struct elf_info *lib = data;\n\n  assert(map != NULL);\n  assert(data != NULL);\n\n  if (strstr(map->l_name, \"libruby\")) {\n    dbg_printf(\"Found a libruby.so!\\n\");\n    if (lib) {\n      lib->base_addr = (GElf_Addr)map->l_addr;\n      lib->filename = strdup(map->l_name);\n    }\n    libruby = 1;\n    return CB_EXIT;\n  }\n  return CB_CONTINUE;\n}\n\n/*\n * walk_linkmap - walk the linkmap firing a callback along the way.\n *\n * Given:\n *  - cb - a callback function\n *  - data - and any private data to pass through (optional)\n *\n * This function will crawl the linkmap and fire the callback on each item\n * found.\n *\n * If the callback returns CB_CONTINUE, this function will move to the next\n * (if any) item in the link map.\n *\n * If the callback returns CB_EXIT, this function will return immediately.\n */\nstatic void\nwalk_linkmap(linkmap_cb cb, void *data)\n{\n  struct link_map *map = _r_debug.r_map;\n\n  assert(map);\n\n  while (map) {\n    dbg_printf(\"Found a linkmap entry: %s\\n\", map->l_name);\n    if (cb(map, data) == CB_EXIT)\n      break;\n    map = map->l_next;\n  }\n\n  return;\n}\n\n/*\n * has_libruby - check if this ruby binary is linked against libruby.so\n *\n * This function checks if the curreny binary is linked against libruby. If\n * so, it sets libruby = 1, and fill internal state in the elf_info structure.\n *\n * Returns 1 if this binary is linked to libruby.so, 0 if not.\n */\nstatic int\nhas_libruby(struct elf_info *lib)\n{\n  libruby = 0;\n  walk_linkmap(find_libruby_cb, lib);\n  return libruby;\n}\n\nsize_t\nbin_type_size(const char *name)\n{\n  Dwarf_Unsigned size = 0;\n  Dwarf_Error error;\n  int res = DW_DLV_ERROR;\n  Dwarf_Die die = 0;\n\n  die = find_die(name, DW_TAG_structure_type);\n\n  if (die) {\n    res = dwarf_bytesize(die, &size, &error);\n    dwarf_dealloc(dwrf,die,DW_DLA_DIE);\n    if (res == DW_DLV_OK)\n      return size;\n  }\n\n  return 0;\n}\n\nint\nbin_type_member_offset(const char *type, const char *member)\n{\n  Dwarf_Error error;\n  int res = DW_DLV_ERROR;\n  Dwarf_Die die = 0, child = 0;\n  Dwarf_Attribute attr = 0;\n\n  die = find_die(type, DW_TAG_structure_type);\n\n  if (die) {\n    child = search_dies(die, member, DW_TAG_member);\n    dwarf_dealloc(dwrf,die,DW_DLA_DIE);\n\n    if (child) {\n      res = dwarf_attr(child, DW_AT_data_member_location, &attr, &error);\n      if (res == DW_DLV_OK) {\n        Dwarf_Locdesc *locs = 0;\n        Dwarf_Signed num = 0;\n\n        res = dwarf_loclist(attr, &locs, &num, &error);\n        if (res == DW_DLV_OK && num > 0) {\n          return locs[0].ld_s[0].lr_number;\n        }\n      }\n    }\n  }\n\n  return -1;\n}\n\n/*\n * open_elf - Opens a file from disk and gets the elf reader started.\n *\n * Given a filename, this function attempts to open the file and start the\n * elf reader.\n *\n * Returns an elf_info object that must be freed by the caller.\n */\nstatic void\nopen_elf(struct elf_info *info)\n{\n\n  assert(info != NULL);\n\n  if (elf_version(EV_CURRENT) == EV_NONE)\n    errx(EX_SOFTWARE, \"ELF library initialization failed: %s\", elf_errmsg(-1));\n\n  if ((info->fd = open(info->filename, O_RDONLY, 0)) < 0)\n    err(EX_NOINPUT, \"open \\%s\\\" failed\", info->filename);\n\n  if ((info->elf = elf_begin(info->fd, ELF_C_READ, NULL)) == NULL)\n    errx(EX_SOFTWARE, \"elf_begin() failed: %s.\", elf_errmsg(-1));\n\n  if (elf_kind(info->elf) != ELF_K_ELF)\n    errx(EX_DATAERR, \"%s is not an ELF object.\", info->filename);\n\n  return;\n}\n\nstatic char *\nget_debuglink_info(struct elf_info *elf, unsigned long *crc_out)\n{\n  char *basename = (char *) elf->debuglink_data->d_buf;\n  unsigned long offset = strlen(basename) + 1;\n  offset = (offset + 3) & ~3;\n\n  memcpy(crc_out, elf->debuglink_data->d_buf + offset, 4);\n\n  return basename;\n}\n\nstatic int\nverify_debug_checksum(const char *filename, unsigned long crc)\n{\n  struct stat stat_buf;\n  void *region = NULL;\n  int fd = open(filename, O_RDONLY);\n  unsigned long crc_check = 0;\n\n  if (fd == -1) {\n    dbg_printf(\"Couldn't open debug file: %s, because: %s\\n\", filename, strerror(errno));\n    return 1;\n  }\n\n  if (fstat(fd, &stat_buf) == -1) {\n    dbg_printf(\"Couldn't stat debug file: %s, because: %s\\n\", filename, strerror(errno));\n    return 1;\n  }\n\n  region = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n  if (region == MAP_FAILED) {\n    dbg_printf(\"mapping a section of size: %zd has failed!\\n\", stat_buf.st_size);\n    return 1;\n  }\n\n  crc_check = gnu_debuglink_crc32(crc_check, region, stat_buf.st_size);\n  dbg_printf(\"supposed crc: %lx, computed crc:  %lx\\n\", crc, crc_check);\n\n  munmap(region, stat_buf.st_size);\n  close(fd);\n\n  return !(crc == crc_check);\n}\n\nstatic int\nfind_debug_syms(struct elf_info *elf)\n{\n  /*\n   * XXX TODO perhaps allow users to specify an env var (or something) pointing\n   * to where the debug symbols live?\n   */\n  unsigned long crc = 0;\n  char *basename = get_debuglink_info(elf, &crc);\n  char *debug_file = NULL, *dir = NULL;\n  char *tmp = strdup(elf->filename);\n\n  assert(basename != NULL);\n  dbg_printf(\".gnu_debuglink base file name: %s, crc: %lx\\n\", basename, crc);\n\n  dir = dirname(tmp);\n  debug_file = calloc(1, strlen(DEBUGDIR) + strlen(dir) +\n                         strlen(\"/\") + strlen(basename) + 1);\n\n  strncat(debug_file, DEBUGDIR, strlen(DEBUGDIR));\n  strncat(debug_file, dir, strlen(dir));\n  strncat(debug_file, \"/\", strlen(\"/\"));\n  strncat(debug_file, basename, strlen(basename));\n\n  elf->debug_data = calloc(1, sizeof(*elf->debug_data));\n  elf->debug_data->filename = debug_file;\n\n  dbg_printf(\"Possible debug symbols in: %s\\n\", elf->debug_data->filename);\n\n  if (verify_debug_checksum(elf->debug_data->filename, crc)) {\n    dbg_printf(\"Checksum verification of debug file: %s failed!\", elf->debug_data->filename);\n    free(debug_file);\n    free(elf->debug_data);\n    return 1;\n  }\n\n  open_elf(elf->debug_data);\n\n  if (dissect_elf(elf->debug_data, 0) != 0) {\n    /* free debug_data */\n    dbg_printf(\"Dissection of debug data failed!\\n\");\n    free(debug_file);\n    free(elf->debug_data);\n    return 1;\n  }\n\n  elf->symtab_data = elf->debug_data->symtab_data;\n  elf->symtab_shdr = elf->debug_data->symtab_shdr;\n\n  /* XXX free stuff */\n  /* LEAK elf_end(elf->elf); */\n\n  elf->elf = elf->debug_data->elf;\n  close(elf->debug_data->fd);\n\n  dbg_printf(\"Finished dissecting debug data\\n\");\n  return 0;\n}\n\n/*\n * dissect_elf - Parses and stores internal data about an ELF object.\n *\n * Given an elf_info structure, this function will attempt to parse the object\n * and store important state needed to rewrite the object later.\n *\n * TODO better error handling\n *\n * Returns 1 on hard errors.\n * Returns 2 on recoverable errors (missing symbol table).\n * Returns 0 on success.\n */\nstatic int\ndissect_elf(struct elf_info *info, int find_debug)\n{\n  assert(info != NULL);\n\n  size_t shstrndx = 0, j = 0;\n  Elf *elf = info->elf;\n  Elf_Scn *scn = NULL;\n  GElf_Shdr shdr;\n  int ret = 0;\n\n  if (elf_getshdrstrndx(elf, &shstrndx) == -1) {\n    dbg_printf(\"getshstrndx() failed: %s.\", elf_errmsg(-1));\n    ret = 1;\n    goto out;\n  }\n\n  if (gelf_getehdr(elf, &(info->ehdr)) == NULL) {\n    dbg_printf(\"Couldn't get elf header.\");\n    ret = 1;\n    goto out;\n  }\n\n  /* search each ELF header and store important data for each header... */\n  while ((scn = elf_nextscn(elf, scn)) != NULL) {\n    if (gelf_getshdr(scn, &shdr) != &shdr) {\n      dbg_printf(\"getshdr() failed: %s.\", elf_errmsg(-1));\n      ret = 1;\n      goto out;\n    }\n\n    /*\n     * The .dynamic section contains entries that are important to memprof.\n     * Specifically, the .rela.plt section information. The .rela.plt section\n     * indexes the .plt, which will be important for hooking functions in\n     * shared objects.\n     */\n    if (shdr.sh_type == SHT_DYNAMIC) {\n      Elf_Data *data;\n      data = elf_getdata(scn, NULL);\n      /* for each entry in the dyn section...  */\n      for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {\n        GElf_Dyn dyn;\n        if (gelf_getdyn(data, j, &dyn) == NULL) {\n          dbg_printf(\"Couldn't get .dynamic data from loaded library.\");\n          ret = 1;\n          goto out;\n        }\n\n        if (dyn.d_tag == DT_JMPREL) {\n          info->relplt_addr = dyn.d_un.d_ptr;\n        }\n        else if (dyn.d_tag == DT_PLTGOT) {\n          info->got_addr = dyn.d_un.d_ptr;\n        }\n        else if (dyn.d_tag == DT_PLTRELSZ) {\n          info->plt_size = dyn.d_un.d_val;\n        }\n      }\n    }\n    /*\n     * The .dynsym section has useful pieces, too, like the dynamic symbol\n     * table. This table is used when walking the .rela.plt section.\n     */\n    else if (shdr.sh_type == SHT_DYNSYM) {\n      Elf_Data *data;\n\n      info->dynsym = elf_getdata(scn, NULL);\n      info->dynsym_count = shdr.sh_size / shdr.sh_entsize;\n      if (info->dynsym == NULL || elf_getdata(scn, info->dynsym) != NULL) {\n        dbg_printf(\"Couldn't get .dynsym data \");\n        ret = 1;\n        goto out;\n      }\n\n      scn = elf_getscn(elf, shdr.sh_link);\n      if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {\n        dbg_printf(\"Couldn't get section header.\");\n        ret = 1;\n        goto out;\n      }\n\n      data = elf_getdata(scn, NULL);\n      if (data == NULL || elf_getdata(scn, data) != NULL\n          || shdr.sh_size != data->d_size) {// condition true on 32bit: || data->d_off) {\n        dbg_printf(\"Couldn't get .dynstr data\\n\");\n        ret = 1;\n        goto out;\n      }\n\n      info->dynstr = data->d_buf;\n    }\n    /*\n     * Pull out information (start address and length) of the .text section.\n     */\n    else if (shdr.sh_type == SHT_PROGBITS &&\n        (shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)) &&\n        strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), \".text\") == 0) {\n\n      info->text_segment = (void *)shdr.sh_addr + info->base_addr;\n      info->text_segment_len = shdr.sh_size;\n    }\n    /*\n     * Pull out information (start address) of the .plt section.\n     */\n    else if (shdr.sh_type == SHT_PROGBITS) {\n      if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), \".plt\") == 0) {\n        info->plt_addr = shdr.sh_addr;\n      } else if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), \".got.plt\") == 0) {\n        info->got_addr = shdr.sh_addr;\n      } else if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), \".gnu_debuglink\") == 0) {\n        dbg_printf(\"gnu_debuglink section found\\n\", shdr.sh_size);\n        if ((info->debuglink_data = elf_getdata(scn, NULL)) == NULL ||\n             info->debuglink_data->d_size == 0) {\n          dbg_printf(\".gnu_debuglink section existed, but wasn't readable.\\n\");\n          ret = 2;\n          goto out;\n        }\n        dbg_printf(\"gnu_debuglink section read (size: %zd)\\n\", shdr.sh_size);\n      }\n    }\n    /*\n     * The symbol table is also needed for bin_find_symbol\n     */\n    else if (shdr.sh_type == SHT_SYMTAB) {\n      info->symtab_shdr = shdr;\n      if ((info->symtab_data = elf_getdata(scn, info->symtab_data)) == NULL ||\n          info->symtab_data->d_size == 0) {\n        dbg_printf(\"shared lib has a broken symbol table. Is it stripped? \"\n                   \"memprof only works on shared libs that are not stripped!\\n\");\n        ret = 2;\n        goto out;\n      }\n    }\n  }\n\n  /* If this object has no symbol table there's nothing else to do but fail */\n  if (!info->symtab_data) {\n    if (info->debuglink_data) {\n      dbg_printf(\"binary is stripped, but there is debug symbol information. memprof will try to read debug symbols in.\\n\");\n    } else {\n      dbg_printf(\"binary is stripped, and no debug symbol info was found. memprof only works on binaries that are not stripped!\\n\");\n    }\n    ret = 1;\n  }\n\n  /*\n   * Walk the sections, pull out, and store the .plt section\n   */\n  for (j = 1; j < info->ehdr.e_shnum; j++) {\n    scn =  elf_getscn(elf, j);\n    if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {\n      dbg_printf(\"Couldn't get section header from library.\");\n      ret = 1;\n      goto out;\n    }\n\n    if (shdr.sh_addr == info->relplt_addr\n        && shdr.sh_size == info->plt_size) {\n      info->relplt = elf_getdata(scn, NULL);\n      info->relplt_count = shdr.sh_size / shdr.sh_entsize;\n      if (info->relplt == NULL || elf_getdata(scn, info->relplt) != NULL) {\n        dbg_printf(\"Couldn't get .rel*.plt data from\");\n        ret = 1;\n        goto out;\n      }\n      break;\n    }\n  }\n\nout:\n  if (find_debug && ret == 1) {\n    if (info->debuglink_data) {\n      find_debug_syms(info);\n    } else {\n      dbg_printf(\"=== WARNING: Object %s was STRIPPED and had no debuglink section. Nothing left to try.\\n\", info->filename);\n    }\n  }\n  return ret;\n}\n\n\n/*\n * bin_init - initialize the binary parsing/modification layer.\n *\n * This function starts the elf parser and sets up internal state.\n */\nvoid\nbin_init()\n{\n  Dwarf_Error dwrf_err;\n\n  ruby_info = calloc(1, sizeof(*ruby_info));\n\n  if (!ruby_info) {\n    errx(EX_UNAVAILABLE, \"Unable to allocate memory to start binary parsing layer\");\n  }\n\n  if (!has_libruby(ruby_info)) {\n    dbg_printf(\"This ruby binary has no libruby\\n\");\n    char *filename = calloc(1, 255);\n    if (readlink(\"/proc/self/exe\", filename, 255) == -1) {\n      errx(EX_UNAVAILABLE, \"Unable to follow /proc/self/exe symlink: %s\", strerror(errno));\n    }\n    ruby_info->filename = filename;\n    ruby_info->base_addr = 0;\n    dbg_printf(\"The path to the binary is: %s\\n\", ruby_info->filename);\n  }\n\n  open_elf(ruby_info);\n\n  if (dissect_elf(ruby_info, 1) == 2) {\n    errx(EX_DATAERR, \"Error trying to parse elf file: %s\\n\", ruby_info->filename);\n  }\n\n  if (dwarf_elf_init(ruby_info->elf, DW_DLC_READ, NULL, NULL, &dwrf, &dwrf_err) != DW_DLV_OK) {\n    errx(EX_DATAERR, \"unable to read debugging data from binary. was it compiled with -g? is it unstripped?\");\n  }\n\n  dbg_printf(\"bin_init finished\\n\");\n}\n#endif\n"
  },
  {
    "path": "ext/extconf.rb",
    "content": "if RUBY_VERSION >= \"1.9\"\n  STDERR.puts \"\\n\\n\"\n  STDERR.puts \"***************************************************************************************\"\n  STDERR.puts \"************************** ruby 1.9 is not supported (yet) =( *************************\"\n  STDERR.puts \"***************************************************************************************\"\n  exit(1)\nend\n\nrequire 'mkmf'\nrequire 'fileutils'\n\nCWD = File.expand_path(File.dirname(__FILE__))\n\ndef sys(cmd)\n  puts \"  -- #{cmd}\"\n  unless ret = xsystem(cmd)\n    raise \"#{cmd} failed, please report to memprof@tmm1.net with pastie.org link to #{CWD}/mkmf.log\"\n  end\n  ret\nend\n\n###\n# yajl\n\nyajl = File.basename('yajl-1.0.9.tar.gz')\ndir = File.basename(yajl, '.tar.gz')\n\nunless File.exists?(\"#{CWD}/dst/lib/libyajl_ext.a\")\n  puts \"(I'm about to compile yajl.. this will definitely take a while)\"\n\n  Dir.chdir('src') do\n    FileUtils.rm_rf(dir) if File.exists?(dir)\n\n    sys(\"tar zxvf #{yajl}\")\n    Dir.chdir(\"#{dir}/src\") do\n      sys(\"sed -i -e 's,yajl,json,g' *.h *.c api/*.h\")\n      Dir['{,api/}yajl*.{h,c}'].each do |file|\n        FileUtils.mv file, file.gsub('yajl', 'json')\n      end\n\n      FileUtils.mkdir_p \"api/json\"\n      %w[ common parse gen ].each do |f|\n        FileUtils.cp \"api/json_#{f}.h\", 'api/json/'\n      end\n\n      File.open(\"extconf.rb\",'w') do |f|\n        f.puts \"require 'mkmf'; $INCFLAGS[0,0] = '-I./api/ '; create_makefile 'libyajl'\"\n      end\n\n      sys(\"#{Config::CONFIG['bindir']}/#{Config::CONFIG['ruby_install_name']} extconf.rb\")\n      sys(\"make\")\n\n      if RUBY_PLATFORM =~ /darwin/\n        sys(\"libtool -static -o libyajl_ext.a #{Dir['*.o'].join(' ')}\")\n      else\n        sys(\"ar rv libyajl_ext.a #{Dir['*.o'].join(' ')}\")\n      end\n\n      FileUtils.mkdir_p \"#{CWD}/dst/lib\"\n      FileUtils.cp 'libyajl_ext.a', \"#{CWD}/dst/lib\"\n      FileUtils.mkdir_p \"#{CWD}/dst/include\"\n      FileUtils.cp_r 'api/json', \"#{CWD}/dst/include/\"\n    end\n  end\nend\n\n$LIBPATH.unshift \"#{CWD}/dst/lib\"\n$INCFLAGS[0,0] = \"-I#{CWD}/dst/include \"\n\nunless have_library('yajl_ext') and have_header('json/json_gen.h')\n  raise 'Yajl build failed'\nend\n\ndef add_define(name)\n  $defs.push(\"-D#{name}\")\nend\n\nif RUBY_PLATFORM =~ /linux/\n  ###\n  # libelf\n\n  libelf = File.basename('libelf-0.8.13.tar.gz')\n  dir = File.basename(libelf, '.tar.gz')\n\n  unless File.exists?(\"#{CWD}/dst/lib/libelf_ext.a\")\n    puts \"(I'm about to compile libelf.. this will definitely take a while)\"\n\n    Dir.chdir('src') do\n      FileUtils.rm_rf(dir) if File.exists?(dir)\n\n      sys(\"tar zxvf #{libelf}\")\n      Dir.chdir(dir) do\n        ENV['CFLAGS'] = '-fPIC'\n        sys(\"./configure --prefix=#{CWD}/dst --disable-nls --disable-shared\")\n        sys(\"make\")\n        sys(\"make install\")\n      end\n    end\n\n    Dir.chdir('dst/lib') do\n      FileUtils.ln_s 'libelf.a', 'libelf_ext.a'\n    end\n  end\n\n  $LIBPATH.unshift \"#{CWD}/dst/lib\"\n  $INCFLAGS[0,0] = \"-I#{CWD}/dst/include \"\n\n  unless have_library('elf_ext', 'gelf_getshdr')\n    raise 'libelf build failed'\n  end\n\n  ###\n  # libdwarf\n\n  libdwarf = File.basename('libdwarf-20091118.tar.gz')\n  dir = File.basename(libdwarf, '.tar.gz').sub('lib','')\n\n  unless File.exists?(\"#{CWD}/dst/lib/libdwarf_ext.a\")\n    puts \"(I'm about to compile libdwarf.. this will definitely take a while)\"\n\n    Dir.chdir('src') do\n      FileUtils.rm_rf(dir) if File.exists?(dir)\n\n      sys(\"tar zxvf #{libdwarf}\")\n      Dir.chdir(\"#{dir}/libdwarf\") do\n        ENV['CFLAGS'] = \"-fPIC -I#{CWD}/dst/include\"\n        ENV['LDFLAGS'] = \"-L#{CWD}/dst/lib\"\n        sys(\"./configure\")\n        sys(\"make\")\n\n        FileUtils.cp 'libdwarf.a', \"#{CWD}/dst/lib/libdwarf_ext.a\"\n        FileUtils.cp 'dwarf.h', \"#{CWD}/dst/include/\"\n        FileUtils.cp 'libdwarf.h', \"#{CWD}/dst/include/\"\n      end\n    end\n  end\n\n  unless have_library('dwarf_ext')\n    raise 'libdwarf build failed'\n  end\n\n  is_elf = true\n  add_define 'HAVE_ELF'\n  add_define 'HAVE_DWARF'\nend\n\nif have_header('mach-o/dyld.h')\n  is_macho = true\n  add_define 'HAVE_MACH'\n  # XXX How to determine this properly? RUBY_PLATFORM reports \"i686-darwin10.2.0\" on Snow Leopard.\n  add_define \"_ARCH_x86_64_\"\n\n  sizes_of = [\n    'RVALUE',\n    'struct heaps_slot'\n  ]\n\n  offsets_of = {\n    'struct heaps_slot' => %w[ slot limit ],\n    'struct BLOCK' => %w[ body var cref self klass wrapper block_obj orig_thread dyna_vars scope prev ],\n    'struct METHOD' => %w[ klass rklass recv id oid body ]\n  }\n\n  addresses_of = [\n    # 'add_freelist',\n    # 'rb_newobj',\n    # 'freelist',\n    # 'heaps',\n    # 'heaps_used',\n    # 'finalizer_table'\n  ]\n\n  expressions = []\n\n  sizes_of.each do |type|\n    name = type.sub(/^struct\\s*/,'')\n    expressions << [\"sizeof__#{name}\", \"sizeof(#{type})\"]\n  end\n  offsets_of.each do |type, members|\n    name = type.sub(/^struct\\s*/,'')\n    members.each do |member|\n      expressions << [\"offset__#{name}__#{member}\", \"(size_t)&(((#{type} *)0)->#{member})\"]\n    end\n  end\n  addresses_of.each do |name|\n    expressions << [\"address__#{name}\", \"&#{name}\"]\n  end\n\n  pid = fork{sleep while true}\n  output = IO.popen('gdb --interpreter=mi --quiet', 'w+') do |io|\n    io.puts \"attach #{pid}\"\n    expressions.each do |name, expr|\n      io.puts \"-data-evaluate-expression #{expr.dump}\"\n    end\n    io.puts 'quit'\n    io.puts 'y'\n    io.close_write\n    io.read\n  end\n  Process.kill 9, pid\n\n  attach, *results = output.grep(/^\\^/).map{ |l| l.strip }\n  if results.find{ |l| l =~ /^\\^error/ }\n    raise \"Unsupported platform: #{results.inspect}\"\n  end\n\n  values = results.map{ |l| l[/value=\"(.+?)\"/, 1] }\n  vars = Hash[ *expressions.map{|n,e| n }.zip(values).flatten ].each do |name, val|\n    add_define \"#{name}=#{val.split.first}\"\n  end\nend\n\narch = RUBY_PLATFORM[/(.*)-linux/,1]\ncase arch\nwhen \"universal\"\n  arch = 'x86_64'\nwhen 'i486'\n  arch = 'i386'\nend\nadd_define \"_ARCH_#{arch}_\"\n\nif ENV['MEMPROF_DEBUG'] == '1'\n  add_define \"_MEMPROF_DEBUG\"\n  $preload = [\"\\nCFLAGS = -Wall -Wextra -fPIC -ggdb3 -O0\"]\nend\n\nif is_elf or is_macho\n  $objs = Dir['{.,*}/*.c'].map{ |file| file.gsub(/\\.c(pp)?$/, '.o') }\n  create_makefile('memprof')\n\n  makefile = File.read('Makefile')\n  makefile.gsub!('-c $<', '-o $(patsubst %.c,%.o,$<) -c $<')\n  makefile.gsub!(/(CLEANOBJS\\s*=)/, '\\1 */*.o')\n\n  File.open('Makefile', 'w+'){ |f| f.puts(makefile) }\nelse\n  raise 'unsupported platform'\nend\n"
  },
  {
    "path": "ext/i386.c",
    "content": "#if defined (_ARCH_i386_) || defined(_ARCH_i686_)\n\n#include <stdint.h>\n#include <string.h>\n\n#include <sys/mman.h>\n\n#include \"arch.h\"\n#include \"x86_gen.h\"\n\n/* This is the stage 1 inline trampoline for hooking the inlined add_freelist\n * function .\n *\n * NOTE: The original instruction mov %reg, freelist is 7 bytes wide,\n * whereas jmpq $displacement is only 5 bytes wide. We *must* pad out\n * the next two bytes. This will be important to remember below.\n */\nstruct inline_st1_tramp {\n  unsigned char jmp;\n  uint32_t displacement;\n  unsigned char pad;\n} __attribute__((__packed__)) inline_st1_tramp = {\n  .jmp  = '\\xe9',\n  .displacement = 0,\n  .pad = '\\x90',\n};\n\nstruct inline_st1_base_short {\n  unsigned char mov;\n  void *addr;\n} __attribute__((__packed__)) inline_st1_short = {\n  .mov = '\\xa3',\n  .addr = 0,\n};\n\nstruct inline_st1_base_long {\n  unsigned char mov;\n  unsigned char src_reg;\n  void *addr;\n} __attribute__((__packed__)) inline_st1_long = {\n  .mov = '\\x89',\n  .src_reg = 0,\n  .addr = 0\n};\n\nstatic int\narch_check_ins(unsigned char *base)\n{\n\n  /* if the byte is 0xa3 then we're moving from %eax, so\n   * the length is only 5, so we don't need the pad.\n   *\n   * otherwise, we're moving from something else, so the\n   * length is going to be 6 and we need a NOP.\n   */\n\n  /* is it a mov instruction? */\n  if (*base == 0xa3)\n    return 0;\n  else if (*base == 0x89)\n    return 1;\n\n  return -1;\n}\n\nint\narch_insert_inline_st2_tramp(void *addr, void *marker, void *trampoline, void *table_entry)\n{\n  struct inline_st1_base_long *long_base = addr;\n  struct inline_st1_base_short *short_base = addr;\n  struct inline_st1_tramp *st1_tramp = addr;\n  void *mov_target = NULL;\n  size_t pad_length = 0;\n\n  if ((pad_length = arch_check_ins(addr)) == -1)\n    return 1;\n\n  if (pad_length == 0) {\n    mov_target = short_base->addr;\n    default_inline_st2_tramp.mov = 0x90;\n    default_inline_st2_tramp.src_reg = 0xa3;\n    inline_st1_tramp.displacement = table_entry - (void *)(short_base + 1);\n    default_inline_st2_tramp.jmp_displacement = (void *)(short_base + 1) - (table_entry + sizeof(default_inline_st2_tramp));\n  } else {\n    mov_target = long_base->addr;\n    default_inline_st2_tramp.mov = long_base->mov;\n    default_inline_st2_tramp.src_reg = long_base->src_reg;\n    inline_st1_tramp.displacement = table_entry - (void *)(long_base + 1) + 1;\n    default_inline_st2_tramp.jmp_displacement = (void *)(long_base + 1) - (table_entry + sizeof(default_inline_st2_tramp));\n  }\n\n  if (marker == mov_target) {\n    default_inline_st2_tramp.mov_addr= default_inline_st2_tramp.frame.freelist = marker;\n    default_inline_st2_tramp.frame.fn_addr = trampoline;\n    if (pad_length) {\n      copy_instructions(addr, &inline_st1_tramp, sizeof(inline_st1_tramp));\n    } else {\n      copy_instructions(addr, &inline_st1_tramp, sizeof(inline_st1_tramp) - 1);\n    }\n      memcpy(table_entry, &default_inline_st2_tramp, sizeof(default_inline_st2_tramp));\n    return 0;\n  }\n\n  return 1;\n}\n#endif\n"
  },
  {
    "path": "ext/i386.h",
    "content": "#if !defined(_i386_h_)\n#define _i386_h_\n\n#include <stdint.h>\n#include \"arch.h\"\n\n/*\n * This is the \"normal\" stage 2 trampoline with a default entry pre-filled\n */\nstatic struct tramp_st2_entry {\n  unsigned char ebx_save;\n  unsigned char mov;\n  void *addr;\n  unsigned char call[2];\n  unsigned char ebx_restore;\n  unsigned char ret;\n} __attribute__((__packed__)) default_st2_tramp = {\n  .ebx_save      = 0x53,            /* push ebx */\n  .mov           = 0xbb,            /* mov addr into ebx */\n  .addr          = 0,               /* this is filled in later */\n  .call          = {0xff, 0xd3},    /* calll *ebx */\n  .ebx_restore   = 0x5b,            /* pop ebx */\n  .ret           = 0xc3,            /* ret */\n};\n\n\n/*\n * This is the inline stage 2 trampoline with a default entry pre-filled\n */\nstatic struct inline_tramp_st2_entry {\n\n  /* this block will be filled in at runtime to replicate the overwritten\n   * instruction.\n   */\n  unsigned char mov;\n  unsigned char src_reg;\n  void *mov_addr;\n\n  /* this frame will arrange freelist to be passed as an argument to\n   * the third and final trampoline (C level).\n   */\n  struct {\n    unsigned char push_ebx;\n    unsigned char pushl[2];\n    void * freelist;\n    unsigned char mov_ebx;\n    void * fn_addr;\n    unsigned char call[2];\n    unsigned char pop_ebx;\n    unsigned char restore_ebx;\n  } __attribute__((__packed__)) frame;\n\n  /* this block jumps back to resume execution */\n  unsigned char jmp;\n  uint32_t jmp_displacement;\n}  __attribute__((__packed__)) default_inline_st2_tramp = {\n  .mov     = 0x89,\n  .src_reg = 0,\n  .mov_addr = 0,\n\n  .frame = {\n   .push_ebx = 0x53,\n   .pushl = {0xff, 0x35},\n   .freelist = 0,\n   .mov_ebx = 0xbb,\n   .fn_addr = 0,\n   .call = {0xff, 0xd3},\n   .pop_ebx = 0x5b,\n   .restore_ebx = 0x5b,\n  },\n\n  .jmp  = 0xe9,\n  .jmp_displacement = 0,\n};\n#endif\n"
  },
  {
    "path": "ext/json.c",
    "content": "#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"json.h\"\n\nvoid\njson_gen_reset(json_gen gen)\n{\n  json_gen_clear(gen);\n  assert (gen->state[gen->depth] == json_gen_complete);\n  gen->state[gen->depth] = json_gen_start;\n  gen->print(gen->ctx, \"\\n\", 1);\n}\n\njson_gen_status\njson_gen_cstr(json_gen gen, const char * str)\n{\n  if (!str || str[0] == 0)\n    return json_gen_null(gen);\n  else\n    return json_gen_string(gen, (unsigned char *)str, strlen(str));\n}\n\njson_gen_status\njson_gen_format(json_gen gen, char *format, ...)\n{\n  va_list args;\n  char *str;\n  int bytes_printed = 0;\n\n  json_gen_status ret;\n\n  va_start(args, format);\n  bytes_printed = vasprintf(&str, format, args);\n  assert(bytes_printed != -1);\n  va_end(args);\n\n  ret = json_gen_string(gen, (unsigned char *)str, strlen(str));\n  free(str);\n  return ret;\n}\n\njson_gen_status\njson_gen_pointer(json_gen gen, void* ptr)\n{\n  return json_gen_format(gen, \"0x%x\", ptr);\n}\n"
  },
  {
    "path": "ext/json.h",
    "content": "#if !defined(__JSON__H_)\n#define __JSON__H_\n\n#include <stdarg.h>\n#include <json/json_gen.h>\n\n/* HAX: copied from internal json_gen.c (PATCH json before building instead)\n */\n\ntypedef enum {\n    json_gen_start,\n    json_gen_map_start,\n    json_gen_map_key,\n    json_gen_map_val,\n    json_gen_array_start,\n    json_gen_in_array,\n    json_gen_complete,\n    json_gen_error\n} json_gen_state;\n\nstruct json_gen_t\n{\n    unsigned int depth;\n    unsigned int pretty;\n    const char * indentString;\n    json_gen_state state[YAJL_MAX_DEPTH];\n    json_print_t print;\n    void * ctx; /* json_buf */\n    /* memory allocation routines */\n    json_alloc_funcs alloc;\n};\n\n/* END HAX\n */\n\nvoid\njson_gen_reset(json_gen gen);\n\njson_gen_status\njson_gen_cstr(json_gen gen, const char * str);\n\njson_gen_status\njson_gen_format(json_gen gen, char *format, ...);\n\njson_gen_status\njson_gen_pointer(json_gen gen, void* ptr);\n\n#endif"
  },
  {
    "path": "ext/mach.c",
    "content": "#if defined(HAVE_MACH)\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"mmap.h\"\n#include \"util.h\"\n\n#include <assert.h>\n#include <dlfcn.h>\n#include <err.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sysexits.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n\n#include <mach-o/dyld.h>\n#include <mach-o/getsect.h>\n#include <mach-o/loader.h>\n#include <mach-o/ldsyms.h>\n#include <mach-o/nlist.h>\n#include <mach-o/dyld_images.h>\n#include <mach-o/fat.h>\n\nstruct mach_config {\n  const struct mach_header *hdr;\n  const struct nlist_64 *symbol_table;\n  const struct nlist_64 **sorted_symbol_table;\n  const struct section_64 *symstub_sect;\n  const char *string_table;\n  uint32_t symbol_count;\n  uint32_t string_table_size;\n  intptr_t image_offset;\n  const struct mach_header* load_addr;\n  uint32_t nindirectsyms;\n  uint32_t indirectsymoff;\n  struct mmap_info file;\n  const char *filename;\n  unsigned int index;\n};\n\nstruct symbol_data {\n  const char *name;\n  void *address;\n  uint32_t size;\n  uint32_t index;\n};\n\nstatic struct mach_config ruby_img_cfg;\nextern struct memprof_config memprof_config;\n\n/*\n * The jmp instructions in the dyld stub table are 6 bytes,\n * 2 bytes for the instruction and 4 bytes for the offset operand\n *\n * This jmp does not jump to the offset operand, but instead\n * looks up an absolute address stored at the offset and jumps to that.\n * Offset is the offset from the address of the _next_ instruction sequence.\n *\n * We need to deference the address at this offset to find the real\n * target of the dyld stub entry.\n */\n\nstruct dyld_stub_entry {\n  unsigned char jmp[2];\n  int32_t offset;\n} __attribute((__packed__));\n\n/* Return the jmp target of a stub entry */\n\nstatic inline void*\nget_dyld_stub_target(struct dyld_stub_entry *entry) {\n  // If the instructions match up, then dereference the address at the offset\n  if (entry->jmp[0] == 0xff && entry->jmp[1] == 0x25)\n    return *((void**)((void*)(entry + 1) + entry->offset));\n\n  return NULL;\n}\n\n/* Set the jmp target of a stub entry */\n\nstatic inline void\nset_dyld_stub_target(struct dyld_stub_entry *entry, void *addr) {\n  void *target = (void *)(entry+1) + entry->offset;\n  copy_instructions(target, &addr, sizeof(void *));\n}\n\nstatic inline const char*\nget_symtab_string(struct mach_config *img_cfg, uint32_t stroff);\n\nstatic void\nextract_symbol_data(struct mach_config *img_cfg, struct symbol_data *sym_data);\n\nstatic int\nfind_dyld_image_index(const struct mach_header_64 *hdr);\n\nstatic void *\nfind_stub_addr(const char *symname, struct mach_config *img_cfg)\n{\n  uint64_t i = 0, nsyms = 0;\n  uint32_t symindex = 0;\n  assert(img_cfg && symname);\n  const struct section_64 *sect = img_cfg->symstub_sect;\n\n  if (!sect)\n    return NULL;\n\n  nsyms = sect->size / sect->reserved2;\n\n  for (; i < nsyms; i ++) {\n    uint32_t currsym = sect->reserved1 + i;\n    uint64_t stubaddr = sect->offset + (i * sect->reserved2);\n    uint32_t symoff = 0;\n\n    assert(currsym <= img_cfg->nindirectsyms);\n\n    /* indirect sym entries are just 32bit indexes into the symbol table to the\n     * symbol the stub is referring to.\n     */\n    symoff = img_cfg->indirectsymoff + (i * 4);\n    memcpy(&symindex, (char*)img_cfg->hdr + symoff, 4);\n    symindex = symindex & ((uint32_t) ~(INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS));\n\n    const struct nlist_64 *ent = img_cfg->symbol_table + symindex;\n    const char *string = get_symtab_string(img_cfg, ent->n_un.n_strx);\n\n    if (strcmp(symname, string+1) == 0) {\n      if (stubaddr) {\n        dbg_printf(\"address of stub in %s for %s is %\" PRIx64 \" + %p = \", img_cfg->filename, string, stubaddr, img_cfg->load_addr);\n        stubaddr = (uint64_t)img_cfg->load_addr + stubaddr;\n        dbg_printf(\"%\" PRIx64 \"\\n\", stubaddr);\n        return (void *)stubaddr;\n      }\n    }\n  }\n  dbg_printf(\"couldn't find address of stub: %s in %s\\n\", symname, img_cfg->filename);\n  return NULL;\n}\n\n/*\n * Search all entries in a stub table for the stub that corresponds to trampee_addr,\n * and overwrite to point at our trampoline code.\n * Returns 0 if any tramps were successfully inserted.\n */\n\nstatic int\nupdate_dyld_stub_table(void *table, uint64_t len, void *trampee_addr, struct tramp_st2_entry *tramp)\n{\n  int ret = -1;\n  struct dyld_stub_entry *entry = (struct dyld_stub_entry*) table;\n  void *max_addr = table + len;\n\n  for(; (void*)entry < max_addr; entry++) {\n    void *target = get_dyld_stub_target(entry);\n    if (trampee_addr == target) {\n      set_dyld_stub_target(entry, tramp->addr);\n      ret = 0;\n    }\n  }\n  return ret;\n}\n\n/*\n * Get all DSOs\n */\nstatic const struct dyld_all_image_infos *\ndyld_get_all_images() {\n  static const struct dyld_all_image_infos* (*_dyld_get_all_image_infos)() = NULL;\n  static const struct dyld_all_image_infos *images = NULL;\n\n  if (!_dyld_get_all_image_infos) {\n    _dyld_lookup_and_bind(\"__dyld_get_all_image_infos\", (void**)&_dyld_get_all_image_infos, NULL);\n    assert(_dyld_get_all_image_infos != NULL);\n  }\n\n  if (!images) {\n    images = _dyld_get_all_image_infos();\n    assert(images != NULL);\n  }\n\n  return images;\n}\n\n/*\n * Get info for particular DSO\n */\nstatic const struct dyld_image_info *\ndyld_get_image_info_for_index(int index) {\n  const struct dyld_all_image_infos *images = dyld_get_all_images();\n\n  // Stupid indexes into the infoArray don't match indexes used elsewhere, so we have to loop\n  unsigned int i;\n  const struct mach_header *hdr = _dyld_get_image_header(index);\n\n  for(i=0; i < _dyld_image_count(); i++) {\n    const struct dyld_image_info image = images->infoArray[i];\n    if (hdr == image.imageLoadAddress)\n      return &(images->infoArray[i]);\n  }\n\n  return NULL;\n}\n\n/*\n * This function tells us if the passed header index is something\n * that we should try to update (by looking at it's filename)\n * Only try to update the running executable, or files that match\n * \"libruby.dylib\" or \"*.bundle\" (other C gems)\n */\n\nstatic const struct mach_header *\nshould_update_image(int index) {\n  const struct mach_header *hdr = _dyld_get_image_header(index);\n\n  /* Don't update if it's the memprof bundle */\n  if ((void*)hdr == &_mh_bundle_header)\n    return NULL;\n\n  /* If it's the ruby executable, do it! */\n  if ((void*)hdr == &_mh_execute_header)\n    return hdr;\n\n  /* otherwise, check to see if its a bundle or libruby */\n  const struct dyld_image_info *image = dyld_get_image_info_for_index(index);\n\n  size_t len = strlen(image->imageFilePath);\n\n  if (len >= 6) {\n    const char *possible_bundle = (image->imageFilePath + len - 6);\n    if (strcmp(possible_bundle, \"bundle\") == 0)\n      return hdr;\n  }\n\n  if (len >= 13) {\n    const char *possible_libruby = (image->imageFilePath + len - 13);\n    if (strcmp(possible_libruby, \"libruby.dylib\") == 0)\n      return hdr;\n  }\n\n  return NULL;\n}\n\n/*\n * Attempts to update all necessary code in a given 'section' of a Mach-O image, to redirect\n * the given function to the trampoline. This function takes care of both normal calls as well as\n * shared library cases.\n * Returns 0 if any tramps were successfully inserted.\n */\n\nstatic int\nupdate_mach_section(const struct mach_header *header, const struct section_64 *sect, intptr_t slide, void *trampee_addr, struct tramp_st2_entry *tramp) {\n  int ret = -1;\n  uint64_t len = 0;\n  /*\n   * We should be able to calculate this information from the section_64 struct ourselves,\n   * but I encountered problems going that route, so using this helper function is fine.\n   *\n   * The segment \"__TEXT\" means \"executable code and other read-only data.\" Segments have \"sections\", like \"__text\", \"__const\", etc.\n   * We want \"__text\" for normal function calls, and \"__symbol_stub\" (with variations like \"__symbol_stub1\") for shared lib stubs.\n   */\n  void *section = getsectdatafromheader_64((const struct mach_header_64*)header, \"__TEXT\", sect->sectname, &len) + slide;\n\n  if (strncmp(sect->sectname, \"__symbol_stub\", 13) == 0) {\n    if (update_dyld_stub_table(section, sect->size, trampee_addr, tramp) == 0) {\n      ret = 0;\n    }\n    return ret;\n  }\n\n  if (strcmp(sect->sectname, \"__text\") == 0) {\n    size_t count = 0;\n    for(; count < len; section++, count++) {\n      if (arch_insert_st1_tramp(section, trampee_addr, tramp) == 0) {\n        ret = 0;\n      }\n    }\n  }\n  return ret;\n}\n\n/*\n * For a given Mach-O image, iterates over all segments and their sections, passing\n * the sections to update_mach_section for potential tramping.\n * Returns 0 if any tramps were successfully inserted.\n */\n\nstatic int\nupdate_bin_for_mach_header(const struct mach_header *header, intptr_t slide, void *trampee_addr, struct tramp_st2_entry *tramp) {\n  int ret = -1;\n  int i, j;\n  int lc_count = header->ncmds;\n\n  /* Load commands start immediately after the Mach header.\n   * This as a char* because we need to step it forward by an arbitrary number of bytes,\n   * specified in the 'cmdsize' field of each load command.\n   */\n  const char *lc = ((const char*) header) + sizeof(struct mach_header_64);\n\n  /* Check all the load commands in the object to see if they are segment commands */\n  for (i = 0; i < lc_count; i++, lc += ((struct load_command*)lc)->cmdsize) {\n    if (((struct load_command*)lc)->cmd == LC_SEGMENT_64) {\n      /* If it's a segment command, we want to iterate over each of it's sections. */\n      const struct segment_command_64 *seg = (const struct segment_command_64 *) lc;\n      const struct section_64 * sect = (const struct section_64*)(lc + sizeof(struct segment_command_64));\n      /* Sections start immediately after the segment_command, and are included in the segment's \"cmdsize\" */\n      int section_count = (seg->cmdsize - sizeof(struct segment_command_64)) / sizeof(struct section_64);\n\n      /* Attempt to tramp the sections */\n      for (j=0; j < section_count; j++, sect++) {\n        if (update_mach_section(header, sect, slide, trampee_addr, tramp) == 0) {\n          ret = 0;\n        }\n      }\n    }\n  }\n  return ret;\n}\n\n/* This function takes a pointer to an *in process* mach_header_64\n * and returns it's image index, which is required to specify the image\n * in many dyld functions.\n *\n * This will NOT work for mach objects read manually from a file, since\n * that's just arbitrary data and the dyld system knows nothing about it.\n */\n\nstatic int\nfind_dyld_image_index(const struct mach_header_64 *hdr) {\n  uint32_t i;\n\n  for (i = 0; i < _dyld_image_count(); i++) {\n    const struct mach_header_64 *tmphdr = (const struct mach_header_64*) _dyld_get_image_header(i);\n    if (hdr == tmphdr)\n      return i;\n  }\n\n  errx(EX_SOFTWARE, \"Could not find image index\");\n\n  /* this is to quiet a GCC warning. might be a bug because errx is marked\n   * __dead2/noreturn\n   */\n  return -1;\n}\n\n/*\n * This function compares two nlist_64 structures by their n_value field (address, usually).\n * It is used by qsort in extract_symbol_table.\n */\n\nstatic int\nnlist_cmp(const void *obj1, const void *obj2) {\n  const struct nlist_64 *nlist1 = *(const struct nlist_64**) obj1;\n  const struct nlist_64 *nlist2 = *(const struct nlist_64**) obj2;\n\n  if (nlist1->n_value == nlist2->n_value)\n    return 0;\n  else if (nlist1->n_value < nlist2->n_value)\n    return -1;\n  else\n    return 1;\n}\n\n/*\n * This function sets the passed pointers to a buffer containing the nlist_64 entries,\n * a buffer containing the string data, the number of entries, and the size of the string buffer.\n *\n * The string names of symbols are stored separately from the symbol table.\n * The symbol table entries contain a string 'index', which is an offset into this region.\n *\n * !!! This function allocates memory. symbol_table and string_table should be freed when no longer used !!!\n */\n\nstatic void\nextract_symbol_table(const struct mach_header_64 *hdr, struct mach_config *img_cfg) {\n  const struct nlist_64 **new_symtbl;\n  char *new_strtbl;\n  uint32_t i, j;\n\n  assert(hdr);\n  assert(img_cfg);\n\n  const struct load_command *lc = (const struct load_command *)(hdr + 1);\n\n  for (i = 0; i < hdr->ncmds; i++, (lc = (const struct load_command *)((char *)lc + lc->cmdsize))) {\n    if (lc->cmd == LC_SYMTAB) {\n      // dbg_printf(\"found an LC_SYMTAB load command.\\n\");\n      const struct symtab_command *sc = (const struct symtab_command*) lc;\n      const struct nlist_64 *file_symtbl = (const struct nlist_64*)((const char*)hdr + sc->symoff);\n\n      new_symtbl = malloc(sc->nsyms * sizeof(struct nlist_64*));\n      new_strtbl = malloc(sc->strsize);\n\n      memcpy(new_strtbl, (char*)hdr + sc->stroff, sc->strsize);\n\n      for (j = 0; j < sc->nsyms; j++)\n        new_symtbl[j] = file_symtbl + j;\n\n      qsort(new_symtbl, sc->nsyms, sizeof(struct nlist_64*), &nlist_cmp);\n\n      img_cfg->symbol_table = file_symtbl;\n      img_cfg->sorted_symbol_table = new_symtbl;\n      img_cfg->symbol_count = sc->nsyms;\n\n      img_cfg->string_table = new_strtbl;\n      img_cfg->string_table_size = sc->strsize;\n\n    } else if (lc->cmd == LC_DYSYMTAB) {\n      // dbg_printf(\"found an LC_DYSYMTAB load command.\\n\");\n      const struct dysymtab_command *dynsym = (const struct dysymtab_command *) lc;\n      img_cfg->nindirectsyms = dynsym->nindirectsyms;\n      img_cfg->indirectsymoff = dynsym->indirectsymoff;\n\n    } else if (lc->cmd == LC_SEGMENT_64) {\n      // dbg_printf(\"found an LC_SEGMENT_64 load command.\\n\");\n      const struct segment_command_64 *seg = (const struct segment_command_64 *) lc;\n      uint32_t i = 0;\n      const struct section_64 *asect = (const struct section_64 *)(seg + 1);\n      for(; i < seg->nsects; i++, asect++) {\n        /*\n         * setting up data to find the indirect symbol tables.\n         */\n\n        /* if this section hsa no symbol stubs, then we don't care about it */\n        if ((asect->flags & SECTION_TYPE) != S_SYMBOL_STUBS)\n          continue;\n\n        if (asect->reserved2 == 0) {\n          dbg_printf(\"!!! Found an LC_SEGMET_64 which was marked as having stubs,\"\n              \" but does not have reserved2 set!! %16s.%16s (skipping)\\n\", asect->segname, asect->sectname);\n          continue;\n        }\n\n        // dbg_printf(\"Found a section with symbol stubs: %16s.%16s.\\n\", asect->segname, asect->sectname);\n        img_cfg->symstub_sect = asect;\n      }\n\n    } else {\n      // dbg_printf(\"found another load command that is not being tracked: %\" PRId32 \"\\n\", lc->cmd);\n    }\n  }\n\n  assert(img_cfg->symbol_table && img_cfg->string_table);\n}\n\n/*\n * Return the string at the given offset into the symbol table's string buffer\n */\n\nstatic inline const char*\nget_symtab_string(struct mach_config *img_cfg, uint32_t stroff) {\n  assert(img_cfg);\n  assert(img_cfg->string_table != NULL);\n  assert(stroff < img_cfg->string_table_size);\n  return img_cfg->string_table + stroff;\n}\n\n/*\n * Lookup the address, size, and symbol table index of a symbol given a symbol_data\n * If sym_data is passed with the name set, this function will attempt to fill\n * in the address, etc. If it is passed with the address set, it will attempt\n * to fill in the name.\n */\n\nstatic void\nextract_symbol_data(struct mach_config *img_cfg, struct symbol_data *sym_data)\n{\n  uint32_t i, j;\n\n  assert(img_cfg->symbol_table != NULL);\n  assert(img_cfg->symbol_count > 0);\n\n  for (i=0; i < img_cfg->symbol_count; i++) {\n    // const struct nlist_64 *nlist_entry = img_cfg->sorted_symbol_table[i];\n    const struct nlist_64 *nlist_entry = img_cfg->symbol_table + i;\n    const char *string = NULL;\n\n    string = get_symtab_string(img_cfg, nlist_entry->n_un.n_strx);\n\n    /* Add the slide to get the *real* address in the process. */\n    const uint64_t addr = nlist_entry->n_value;\n    void *ptr = (void*)(addr + img_cfg->image_offset);\n\n    /*\n     * If the user passes a name, match against the name\n     * If the user passes an address, match against that.\n     */\n    if ((sym_data->name && string && strcmp(sym_data->name, string+1) == 0) || (sym_data->address && ptr == sym_data->address)) {\n      if (!sym_data->address)\n        sym_data->address = ptr;\n      if (!sym_data->name)\n        sym_data->name = string+1;\n\n      sym_data->index = i;\n\n      const struct nlist_64 *next_entry = NULL;\n\n      /*\n       * There can be multiple entries in the symbol table with the same n_value (address).\n       * This means that the 'next' one isn't always good enough. We have to make sure it's\n       * really a different symbol.\n       */\n      j = 1;\n      while (next_entry == NULL) {\n        const struct nlist_64 *tmp_entry = img_cfg->sorted_symbol_table[i + j];\n        if (nlist_entry->n_value != tmp_entry->n_value)\n          next_entry = tmp_entry;\n        j++;\n      }\n\n      /*\n       * Subtract our address from the address of the next symbol to get it's rough size.\n       * My observation is that the start of the next symbol will be padded to 16 byte alignment from the end of this one.\n       * This should be fine, since the point of getting the size is just to minimize scan area for tramp insertions.\n       */\n      sym_data->size = (next_entry->n_value - addr);\n      break;\n    }\n  }\n}\n\nstatic void\nfree_mach_config(struct mach_config *cfg) {\n  if (cfg == &ruby_img_cfg)\n    return;\n\n  munmap_file(&cfg->file);\n  free(cfg);\n}\n\nstatic struct mach_config *\nmach_config_for_index(unsigned int index) {\n  if (index >= _dyld_image_count())\n    return NULL;\n\n  if (index == ruby_img_cfg.index)\n    return &ruby_img_cfg;\n\n  const struct dyld_image_info *image = dyld_get_image_info_for_index(index);\n  struct mach_config *cfg = calloc(1, sizeof(struct mach_config));\n\n  cfg->index = index;\n  cfg->file.name = cfg->filename = image->imageFilePath;\n  if (mmap_file_open(&cfg->file) < 0)\n    errx(EX_OSFILE, \"Failed to fread() file %s\", cfg->filename);\n  cfg->image_offset = _dyld_get_image_vmaddr_slide(index);\n  cfg->load_addr = image->imageLoadAddress;\n\n  struct mach_header_64 *hdr = (struct mach_header_64*) cfg->file.data;\n  assert(hdr);\n\n  if (hdr->magic == FAT_CIGAM) {\n    unsigned int j;\n    struct fat_header *fat = (struct fat_header *)hdr;\n\n    for(j=0; j < OSSwapInt32(fat->nfat_arch); j++) {\n      struct fat_arch *arch = (struct fat_arch *)((char*)fat + sizeof(struct fat_header) + sizeof(struct fat_arch) * j);\n\n      if (OSSwapInt32(arch->cputype) == CPU_TYPE_X86_64) {\n        hdr = (struct mach_header_64 *)(cfg->file.data + OSSwapInt32(arch->offset));\n        break;\n      }\n    }\n  }\n\n  if (hdr->magic != MH_MAGIC_64) {\n    printf(\"Magic for Ruby Mach-O file doesn't match\\n\");\n    munmap_file(&cfg->file);\n    free(cfg);\n    return NULL;\n  }\n\n  extract_symbol_table(hdr, cfg);\n  cfg->hdr = (const struct mach_header *)hdr;\n\n  return cfg;\n}\n\nvoid *\nbin_find_symbol(const char *symbol, size_t *size, int search_libs) {\n  struct symbol_data sym_data;\n\n  memset(&sym_data, 0, sizeof(struct symbol_data));\n  sym_data.name = symbol;\n\n  extract_symbol_data(&ruby_img_cfg, &sym_data);\n\n  if (!sym_data.address && search_libs) {\n    int i, header_count = _dyld_image_count();\n\n    for (i=0; i < header_count; i++) {\n      const struct dyld_image_info *image = dyld_get_image_info_for_index(i);\n\n      if ((void*)image->imageLoadAddress == &_mh_bundle_header ||\n          (void*)image->imageLoadAddress == &_mh_execute_header)\n        continue;\n\n      struct mach_config *cfg = mach_config_for_index(i);\n      if (cfg) {\n        extract_symbol_data(cfg, &sym_data);\n\n        if (sym_data.address == image->imageLoadAddress) { // wtf? this happens for mysql_api.bundle with mysql_real_query\n          sym_data.address = 0;\n        } else if (sym_data.address) {\n          if (cfg->image_offset == 0) // another wtf? happens on libSystem.dylib where we need to add load address, but libmysqlclient.dylib etc are fine\n            sym_data.address = (char*)image->imageLoadAddress + (size_t)sym_data.address;\n          dbg_printf(\"found symbol %s in %s: %p\\n\", sym_data.name, image->imageFilePath, sym_data.address);\n          free_mach_config(cfg);\n          break;\n        }\n        free_mach_config(cfg);\n      }\n    }\n  }\n\n  if (size)\n    *size = sym_data.size;\n  return sym_data.address;\n}\n\n/*\n * Do the same thing as in bin_find_symbol above, but compare addresses and return the string name.\n */\nconst char *\nbin_find_symbol_name(void *symbol) {\n  struct symbol_data sym_data;\n\n  memset(&sym_data, 0, sizeof(struct symbol_data));\n  sym_data.address = symbol;\n\n  extract_symbol_data(&ruby_img_cfg, &sym_data);\n\n  return sym_data.name;\n}\n\n/*\n * I will explain bin_update_image with imaginary Ruby code:\n *\n * Process.mach_images.each do |image|\n *   image.segments.each do |segment|\n *     segment.sections.each do |section|\n *       if section.name == \"__text\"\n *         tramp_normal_callsites(section)\n *       elsif section.name =~ /__symbol_stub/ && image.filename =~ /libruby\\.dylib|bundle/\n *         tramp_dyld_stubs(section)\n *       end\n *     end\n *   end\n * end\n */\n\nint\nbin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig_function)\n{\n  int ret = -1;\n  int i;\n  int header_count = _dyld_image_count();\n  void *trampee_addr = bin_find_symbol(trampee, NULL, 0);\n\n  // Go through all the mach objects that are loaded into this process\n  for (i=0; i < header_count; i++) {\n    const struct mach_header *current_hdr = NULL;\n\n    if ((void*)_dyld_get_image_header(i) == &_mh_bundle_header)\n      continue; // always ignore memprof.bundle\n\n    struct mach_config *cfg = mach_config_for_index(i);\n    if (cfg->filename && strstr(cfg->filename, \"libSystem\") != NULL) {\n      free_mach_config(cfg);\n      continue; // ignore libSystem\n    }\n\n    void *stub = find_stub_addr(trampee, cfg);\n\n    if (stub) {\n      struct dyld_stub_entry *entry = (struct dyld_stub_entry *)stub;\n      if (orig_function)\n        *orig_function = get_dyld_stub_target(entry);\n      set_dyld_stub_target(entry, tramp->addr);\n\n      ret = 0;\n\n    } else if (trampee_addr) {\n      if ((current_hdr = should_update_image(i)) == NULL)\n        continue;\n\n      if (update_bin_for_mach_header(current_hdr, _dyld_get_image_vmaddr_slide(i), trampee_addr, tramp) == 0) {\n        ret = 0;\n        if (orig_function)\n          *orig_function = trampee_addr;\n      }\n    }\n\n    free_mach_config(cfg);\n  }\n\n  return ret;\n}\n\nvoid *\ndo_bin_allocate_page(struct mach_config *cfg)\n{\n  void *ret = NULL;\n  void *addr = (void *)cfg->load_addr;\n  size_t i = 0;\n\n  dbg_printf(\"ruby loaded at: %p\\n\", addr);\n\n  /*\n   * XXX no clue how large the text segment is, so guess.\n   * TODO remove this.\n   */\n  addr += 65535;\n\n  for (; i < INT_MAX - memprof_config.pagesize; i += memprof_config.pagesize, addr += memprof_config.pagesize) {\n    ret = mmap(addr, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC,\n               MAP_ANON|MAP_PRIVATE, -1, 0);\n\n    if (ret != MAP_FAILED) {\n      dbg_printf(\"found a page at: %p\\n\", ret);\n      memset(ret, 0x90, memprof_config.pagesize);\n      return ret;\n    }\n  }\n  return NULL;\n}\n\nvoid *\nbin_allocate_page()\n{\n  return do_bin_allocate_page(&ruby_img_cfg);\n}\n\nsize_t\nbin_type_size(const char *type)\n{\n  (void) type;\n  return 0;\n}\n\nint\nbin_type_member_offset(const char *type, const char *member)\n{\n  (void) type;\n  (void) member;\n  return -1;\n}\n\nvoid\nbin_init()\n{\n  void *ptr = NULL;\n  int index = 0;\n  Dl_info info;\n\n  memset(&ruby_img_cfg, 0, sizeof(struct mach_config));\n\n  // We can use this is a reasonably sure method of finding the file\n  // that the Ruby junk resides in.\n  ptr = dlsym(RTLD_DEFAULT, \"rb_newobj\");\n\n  if (!ptr)\n    errx(EX_SOFTWARE, \"Could not find rb_newobj in this process. WTF???\");\n\n  if (!dladdr(ptr, &info) || !info.dli_fname)\n    errx(EX_SOFTWARE, \"Could not find the Mach object associated with rb_newobj.\");\n\n  ruby_img_cfg.file.name = ruby_img_cfg.filename = info.dli_fname;\n  if (mmap_file_open(&ruby_img_cfg.file) < 0)\n    errx(EX_OSFILE, \"Failed to fread() file %s\", ruby_img_cfg.filename);\n  struct mach_header_64 *hdr = (struct mach_header_64*) ruby_img_cfg.file.data;\n  assert(hdr);\n  ruby_img_cfg.hdr = (const struct mach_header *)hdr;\n\n  if (hdr->magic != MH_MAGIC_64)\n    errx(EX_SOFTWARE, \"Magic for Ruby Mach-O file doesn't match\");\n\n  index = find_dyld_image_index((const struct mach_header_64*) info.dli_fbase);\n  ruby_img_cfg.image_offset = _dyld_get_image_vmaddr_slide(index);\n  ruby_img_cfg.index = index;\n  ruby_img_cfg.load_addr = dyld_get_image_info_for_index(index)->imageLoadAddress;\n\n  extract_symbol_table(hdr, &ruby_img_cfg);\n\n  assert(ruby_img_cfg.symbol_table != NULL);\n  assert(ruby_img_cfg.string_table != NULL);\n  assert(ruby_img_cfg.symbol_count > 0);\n\n  // XXX: do not free this, since we're using the symbol and string tables from inside the file\n  // free(hdr);\n}\n#endif\n"
  },
  {
    "path": "ext/memprof.c",
    "content": "#include <ruby.h>\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <assert.h>\n#include <err.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sysexits.h>\n\n#include <st.h>\n#include <intern.h>\n#include <node.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\n/*\n * bleak_house stuff\n */\nstatic VALUE eUnsupported;\nstatic int track_objs = 0;\nstatic int memprof_started = 0;\nstatic st_table *objs = NULL;\n\n/*\n * stuff needed for heap dumping\n */\nstatic VALUE (*rb_classname)(VALUE);\nstatic RUBY_DATA_FUNC *rb_bm_mark;\nstatic RUBY_DATA_FUNC *rb_blk_free;\nstatic RUBY_DATA_FUNC *rb_thread_mark;\nstruct memprof_config memprof_config;\n\n/*\n * memprof config struct init\n */\nstatic void init_memprof_config_base();\nstatic void init_memprof_config_extended();\n\nstruct obj_track {\n  VALUE obj;\n  char *source;\n  int line;\n  int len;\n  struct timeval time[];\n};\n\nstatic VALUE gc_hook;\nstatic void **ptr_to_rb_mark_table_add_filename = NULL;\nstatic void (*rb_mark_table_add_filename)(char*);\nstatic void (*rb_add_freelist)(VALUE);\n\nstatic int\nree_sourcefile_mark_each(st_data_t key, st_data_t val, st_data_t arg)\n{\n  struct obj_track *tracker = (struct obj_track *)val;\n  assert(tracker != NULL);\n\n  if (tracker->source)\n    rb_mark_table_add_filename(tracker->source);\n  return ST_CONTINUE;\n}\n\nstatic int\nmri_sourcefile_mark_each(st_data_t key, st_data_t val, st_data_t arg)\n{\n  struct obj_track *tracker = (struct obj_track *)val;\n  assert(tracker != NULL);\n\n  if (tracker->source)\n    (tracker->source)[-1] = 1;\n  return ST_CONTINUE;\n}\n\n/* Accomodate the different source file marking techniques of MRI and REE.\n *\n * The function pointer for REE changes depending on whether COW is enabled,\n * which can be toggled at runtime. We need to deference it to get the\n * real function every time we come here, as it may have changed.\n */\n\nstatic void\nsourcefile_marker()\n{\n  if (ptr_to_rb_mark_table_add_filename) {\n    rb_mark_table_add_filename = *ptr_to_rb_mark_table_add_filename;\n    assert(rb_mark_table_add_filename != NULL);\n    st_foreach(objs, ree_sourcefile_mark_each, (st_data_t)NULL);\n  } else {\n    st_foreach(objs, mri_sourcefile_mark_each, (st_data_t)NULL);\n  }\n}\n\nstatic VALUE\nnewobj_tramp()\n{\n  VALUE ret = rb_newobj();\n  struct obj_track *tracker = NULL;\n\n  if (track_objs && objs) {\n    tracker = malloc(sizeof(*tracker) + sizeof(struct timeval));\n\n    if (tracker) {\n      if (ruby_current_node && ruby_current_node->nd_file &&\n          *ruby_current_node->nd_file) {\n        tracker->source = ruby_current_node->nd_file;\n        tracker->line = nd_line(ruby_current_node);\n      } else if (ruby_sourcefile) {\n        tracker->source = ruby_sourcefile;\n        tracker->line = ruby_sourceline;\n      } else {\n        tracker->source = NULL;\n        tracker->line = 0;\n      }\n\n      tracker->obj = ret;\n      tracker->len = 1;\n\n      /* TODO a way for the user to disallow time tracking */\n      if (gettimeofday(&tracker->time[0], NULL) == -1) {\n        perror(\"gettimeofday failed. Continuing anyway, error\");\n      }\n\n      rb_gc_disable();\n      st_insert(objs, (st_data_t)ret, (st_data_t)tracker);\n      rb_gc_enable();\n    } else {\n      fprintf(stderr, \"Warning, unable to allocate a tracker. \"\n              \"You are running dangerously low on RAM!\\n\");\n    }\n  }\n\n  return ret;\n}\n\nstatic void\nfreelist_tramp(unsigned long rval)\n{\n  struct obj_track *tracker = NULL;\n\n  if (rb_add_freelist) {\n    rb_add_freelist(rval);\n  }\n\n  if (track_objs && objs) {\n    st_delete(objs, (st_data_t *) &rval, (st_data_t *) &tracker);\n    if (tracker) {\n      free(tracker);\n    }\n  }\n}\n\nstatic int\nobjs_free(st_data_t key, st_data_t record, st_data_t arg)\n{\n  struct obj_track *tracker = (struct obj_track *)record;\n  free(tracker);\n  return ST_DELETE;\n}\n\nstatic int\nobjs_tabulate(st_data_t key, st_data_t record, st_data_t arg)\n{\n  st_table *table = (st_table *)arg;\n  struct obj_track *tracker = (struct obj_track *)record;\n  char *source_key = NULL;\n  unsigned long count = 0;\n  char *type = NULL;\n  int bytes_printed = 0;\n\n  switch (TYPE(tracker->obj)) {\n    case T_NONE:\n      type = \"__none__\"; break;\n    case T_BLKTAG:\n      type = \"__blktag__\"; break;\n    case T_UNDEF:\n      type = \"__undef__\"; break;\n    case T_VARMAP:\n      type = \"__varmap__\"; break;\n    case T_SCOPE:\n      type = \"__scope__\"; break;\n    case T_NODE:\n      type = \"__node__\"; break;\n    default:\n      if (RBASIC(tracker->obj)->klass) {\n        type = (char*) rb_obj_classname(tracker->obj);\n      } else {\n        type = \"__unknown__\";\n      }\n  }\n\n  bytes_printed = asprintf(&source_key, \"%s:%d:%s\", tracker->source ? tracker->source : \"__null__\", tracker->line, type);\n  assert(bytes_printed != -1);\n  st_lookup(table, (st_data_t)source_key, (st_data_t *)&count);\n  if (st_insert(table, (st_data_t)source_key, ++count)) {\n    free(source_key);\n  }\n\n  return ST_CONTINUE;\n}\n\nstruct results {\n  char **entries;\n  size_t num_entries;\n};\n\nstatic int\nobjs_to_array(st_data_t key, st_data_t record, st_data_t arg)\n{\n  struct results *res = (struct results *)arg;\n  unsigned long count = (unsigned long)record;\n  char *source = (char *)key;\n  int bytes_printed = 0;\n\n  bytes_printed = asprintf(&(res->entries[res->num_entries++]), \"%7li %s\", count, source);\n  assert(bytes_printed != -1);\n\n  free(source);\n  return ST_DELETE;\n}\n\nstatic VALUE\nmemprof_start(VALUE self)\n{\n  if (!memprof_started) {\n    insert_tramp(\"rb_newobj\", newobj_tramp);\n    insert_tramp(\"add_freelist\", freelist_tramp);\n    memprof_started = 1;\n  }\n\n  if (track_objs == 1)\n    return Qfalse;\n\n  track_objs = 1;\n  return Qtrue;\n}\n\nstatic VALUE\nmemprof_stop(VALUE self)\n{\n  /* TODO: remove trampolines and set memprof_started = 0 */\n\n  if (track_objs == 0)\n    return Qfalse;\n\n  track_objs = 0;\n  st_foreach(objs, objs_free, (st_data_t)0);\n  return Qtrue;\n}\n\nstatic int\nmemprof_strcmp(const void *obj1, const void *obj2)\n{\n  char *str1 = *(char **)obj1;\n  char *str2 = *(char **)obj2;\n  return strcmp(str2, str1);\n}\n\nstatic VALUE\nmemprof_stats(int argc, VALUE *argv, VALUE self)\n{\n  st_table *tmp_table;\n  struct results res;\n  size_t i;\n  VALUE str;\n  FILE *out = NULL;\n\n  if (!track_objs)\n    rb_raise(rb_eRuntimeError, \"object tracking disabled, call Memprof.start first\");\n\n  rb_scan_args(argc, argv, \"01\", &str);\n\n  if (RTEST(str)) {\n    out = fopen(StringValueCStr(str), \"w\");\n    if (!out)\n      rb_raise(rb_eArgError, \"unable to open output file\");\n  }\n\n  track_objs = 0;\n\n  tmp_table = st_init_strtable();\n  st_foreach(objs, objs_tabulate, (st_data_t)tmp_table);\n\n  res.num_entries = 0;\n  res.entries = malloc(sizeof(char*) * tmp_table->num_entries);\n\n  st_foreach(tmp_table, objs_to_array, (st_data_t)&res);\n  st_free_table(tmp_table);\n\n  qsort(res.entries, res.num_entries, sizeof(char*), &memprof_strcmp);\n\n  for (i=0; i < res.num_entries; i++) {\n    fprintf(out ? out : stderr, \"%s\\n\", res.entries[i]);\n    free(res.entries[i]);\n  }\n  free(res.entries);\n\n  if (out)\n    fclose(out);\n\n  track_objs = 1;\n  return Qnil;\n}\n\nstatic VALUE\nmemprof_stats_bang(int argc, VALUE *argv, VALUE self)\n{\n  memprof_stats(argc, argv, self);\n  st_foreach(objs, objs_free, (st_data_t)0);\n  return Qnil;\n}\n\nstatic void\njson_print(void *ctx, const char * str, unsigned int len)\n{\n  FILE *out = (FILE *)ctx;\n  size_t written = 0;\n  while(1) {\n    written += fwrite(str + written, sizeof(char), len - written, out ? out : stdout);\n    if (written == len) break;\n  }\n  if (str && len > 0 && str[0] == '\\n' && out)\n    fflush(out);\n}\n\nstatic VALUE\nmemprof_track(int argc, VALUE *argv, VALUE self)\n{\n  if (!rb_block_given_p())\n    rb_raise(rb_eArgError, \"block required\");\n\n  memprof_start(self);\n  rb_yield(Qnil);\n  memprof_stats(argc, argv, self);\n  memprof_stop(self);\n  return Qnil;\n}\n\nstatic json_gen_status\njson_gen_id(json_gen gen, ID id)\n{\n  if (id) {\n    if (id < 100)\n      return json_gen_format(gen, \":%c\", id);\n    else\n      return json_gen_format(gen, \":%s\", rb_id2name(id));\n  } else\n    return json_gen_null(gen);\n}\n\nstatic json_gen_status\njson_gen_value(json_gen gen, VALUE obj)\n{\n  if (FIXNUM_P(obj))\n    return json_gen_integer(gen, NUM2LONG(obj));\n  else if (NIL_P(obj) || obj == Qundef)\n    return json_gen_null(gen);\n  else if (obj == Qtrue)\n    return json_gen_bool(gen, 1);\n  else if (obj == Qfalse)\n    return json_gen_bool(gen, 0);\n  else if (SYMBOL_P(obj))\n    return json_gen_id(gen, SYM2ID(obj));\n  else\n    return json_gen_pointer(gen, (void*)obj);\n}\n\nstatic json_gen_config fancy_conf = { .beautify = 1, .indentString = \"  \" };\nstatic json_gen_config basic_conf = { .beautify = 0, .indentString = \"  \" };\n\nstatic json_gen\njson_for_args(int argc, VALUE *argv)\n{\n  FILE *out = NULL;\n  VALUE str;\n  rb_scan_args(argc, argv, \"01\", &str);\n\n  if (RTEST(str)) {\n    out = fopen(StringValueCStr(str), \"w\");\n    if (!out)\n      rb_raise(rb_eArgError, \"unable to open output file\");\n  }\n\n  if (!out)\n    out = stderr;\n\n  json_gen gen = json_gen_alloc2((json_print_t)&json_print, out == stderr ? &fancy_conf : &basic_conf, NULL, (void*)out);\n\n  return gen;\n}\n\nstatic void\njson_free(json_gen gen)\n{\n  FILE *out = (FILE*)gen->ctx;\n  if (out != stderr)\n    fclose(out);\n  json_gen_free(gen);\n}\n\nstatic VALUE\nmemprof_trace(int argc, VALUE *argv, VALUE self)\n{\n  if (!rb_block_given_p())\n    rb_raise(rb_eArgError, \"block required\");\n\n  json_gen gen = json_for_args(argc, argv);\n\n  trace_set_output(gen);\n  json_gen_map_open(gen);\n\n  trace_invoke_all(TRACE_RESET);\n  trace_invoke_all(TRACE_START);\n\n  VALUE ret = rb_yield(Qnil);\n\n  trace_invoke_all(TRACE_DUMP);\n  trace_invoke_all(TRACE_STOP);\n\n  json_gen_map_close(gen);\n  json_gen_reset(gen);\n\n  json_free(gen);\n  trace_set_output(NULL);\n\n  return ret;\n}\n\nstatic int\neach_request_entry(st_data_t key, st_data_t record, st_data_t arg)\n{\n  json_gen gen = (json_gen)arg;\n  VALUE k = (VALUE)key;\n  VALUE v = (VALUE)record;\n\n  if (RTEST(v) && BUILTIN_TYPE(v) == T_STRING && RTEST(k) && BUILTIN_TYPE(k) == T_STRING &&\n      RSTRING_PTR(k)[0] >= 65 && RSTRING_PTR(k)[0] <= 90) {\n    json_gen_cstr(gen, StringValueCStr(k));\n    json_gen_cstr(gen, StringValueCStr(v));\n  }\n\n  return ST_CONTINUE;\n}\n\nstatic VALUE tracing_json_filename = Qnil;\nstatic json_gen tracing_json_gen = NULL;\n\nstatic VALUE\nmemprof_trace_filename_set(int argc, VALUE *argv, VALUE self)\n{\n  if (tracing_json_gen) {\n    json_free(tracing_json_gen);\n    tracing_json_gen = NULL;\n  }\n\n  if (!RTEST(*argv)) {\n    tracing_json_filename = Qnil;\n  } else {\n    tracing_json_gen = json_for_args(argc, argv);\n    tracing_json_filename = *argv;\n  }\n\n  return tracing_json_filename;\n}\n\nstatic VALUE\nmemprof_trace_filename_get(VALUE self)\n{\n  return tracing_json_filename;\n}\n\nstatic VALUE\nmemprof_trace_request(VALUE self, VALUE env)\n{\n  if (!rb_block_given_p())\n    rb_raise(rb_eArgError, \"block required\");\n\n  uint64_t start_time;\n  uint64_t end_time;\n  char str_time[32];\n\n  json_gen gen;\n  if (tracing_json_gen)\n    gen = tracing_json_gen;\n  else\n    gen = json_for_args(0, NULL);\n\n  json_gen_map_open(gen);\n\n  json_gen_cstr(gen, \"start\");\n  start_time = timeofday_ms();\n  sprintf(str_time, \"%\" PRIu64, start_time);\n  json_gen_number(gen, str_time, strlen(str_time));\n\n  json_gen_cstr(gen, \"tracers\");\n  json_gen_map_open(gen);\n\n  trace_set_output(gen);\n  trace_invoke_all(TRACE_RESET);\n  trace_invoke_all(TRACE_START);\n\n  start_time = timeofday_ms();\n  VALUE ret = rb_yield(Qnil);\n  end_time = timeofday_ms();\n\n  trace_invoke_all(TRACE_DUMP);\n  trace_invoke_all(TRACE_STOP);\n\n  json_gen_map_close(gen);\n\n  if (RTEST(env) && TYPE(env) == T_HASH) {\n    VALUE val, str;\n    val = rb_hash_aref(env, rb_str_new2(\"action_controller.request.path_parameters\"));\n    if (!RTEST(val))\n      val = rb_hash_aref(env, rb_str_new2(\"action_dispatch.request.parameters\"));\n\n    if (RTEST(val) && TYPE(val) == T_HASH) {\n      json_gen_cstr(gen, \"rails\");\n      json_gen_map_open(gen);\n      str = rb_hash_aref(val, rb_str_new2(\"controller\"));\n      if (RTEST(str) && TYPE(str) == T_STRING) {\n        json_gen_cstr(gen, \"controller\");\n        json_gen_cstr(gen, RSTRING_PTR(str));\n      }\n\n      str = rb_hash_aref(val, rb_str_new2(\"action\"));\n      if (RTEST(str) && TYPE(str) == T_STRING) {\n        json_gen_cstr(gen, \"action\");\n        json_gen_cstr(gen, RSTRING_PTR(str));\n      }\n      json_gen_map_close(gen);\n    }\n\n    json_gen_cstr(gen, \"request\");\n    json_gen_map_open(gen);\n    // struct RHash *hash = RHASH(env);\n    // st_foreach(hash->tbl, each_request_entry, (st_data_t)gen);\n\n    #define DUMP_HASH_ENTRY(key) do {                    \\\n      str = rb_hash_aref(env, rb_str_new2(key));         \\\n      if (RTEST(str) &&                                  \\\n          TYPE(str) == T_STRING &&                       \\\n          RSTRING_PTR(str)) {                            \\\n        json_gen_cstr(gen, key);                         \\\n        json_gen_cstr(gen, RSTRING_PTR(str));            \\\n      }                                                  \\\n    } while(0)\n    // DUMP_HASH_ENTRY(\"HTTP_USER_AGENT\");\n    DUMP_HASH_ENTRY(\"REQUEST_PATH\");\n    DUMP_HASH_ENTRY(\"PATH_INFO\");\n    DUMP_HASH_ENTRY(\"REMOTE_ADDR\");\n    DUMP_HASH_ENTRY(\"REQUEST_URI\");\n    DUMP_HASH_ENTRY(\"REQUEST_METHOD\");\n    DUMP_HASH_ENTRY(\"QUERY_STRING\");\n\n    json_gen_map_close(gen);\n  }\n\n  if (RTEST(ret) && TYPE(ret) == T_ARRAY) {\n    json_gen_cstr(gen, \"response\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"code\");\n    json_gen_value(gen, RARRAY_PTR(ret)[0]);\n    json_gen_map_close(gen);\n  }\n\n  json_gen_cstr(gen, \"time\");\n  json_gen_integer(gen, end_time-start_time);\n\n  json_gen_map_close(gen);\n  json_gen_reset(gen);\n\n  if (gen != tracing_json_gen)\n    json_free(gen);\n\n  return ret;\n}\n\n#include \"json.h\"\n#include \"env.h\"\n#include \"rubyio.h\"\n#include \"re.h\"\n\n#ifndef RARRAY_PTR\n#define RARRAY_PTR(ary) RARRAY(ary)->ptr\n#endif\n\n#ifndef RARRAY_LEN\n#define RARRAY_LEN(ary) RARRAY(ary)->len\n#endif\n\n#ifndef RSTRING_PTR\n#define RSTRING_PTR(str) RSTRING(str)->ptr\n#endif\n\n#ifndef RSTRING_LEN\n#define RSTRING_LEN(str) RSTRING(str)->len\n#endif\n\nstatic int\neach_hash_entry(st_data_t key, st_data_t record, st_data_t arg)\n{\n  json_gen gen = (json_gen)arg;\n  VALUE k = (VALUE)key;\n  VALUE v = (VALUE)record;\n\n  json_gen_array_open(gen);\n  json_gen_value(gen, k);\n  json_gen_value(gen, v);\n  json_gen_array_close(gen);\n\n  return ST_CONTINUE;\n}\n\nstatic int\neach_ivar(st_data_t key, st_data_t record, st_data_t arg)\n{\n  json_gen gen = (json_gen)arg;\n  ID id = (ID)key;\n  VALUE val = (VALUE)record;\n  const char *name = rb_id2name(id);\n\n  json_gen_cstr(gen, name ? name : \"(none)\");\n  json_gen_value(gen, val);\n\n  return ST_CONTINUE;\n}\n\nstatic char *\nnd_type_str(VALUE obj)\n{\n  switch(nd_type(obj)) {\n    #define ND(type) case NODE_##type: return #type;\n    ND(METHOD);     ND(FBODY);      ND(CFUNC);    ND(SCOPE);\n    ND(BLOCK);      ND(IF);         ND(CASE);     ND(WHEN);\n    ND(OPT_N);      ND(WHILE);      ND(UNTIL);    ND(ITER);\n    ND(FOR);        ND(BREAK);      ND(NEXT);     ND(REDO);\n    ND(RETRY);      ND(BEGIN);      ND(RESCUE);   ND(RESBODY);\n    ND(ENSURE);     ND(AND);        ND(OR);       ND(NOT);\n    ND(MASGN);      ND(LASGN);      ND(DASGN);    ND(DASGN_CURR);\n    ND(GASGN);      ND(IASGN);      ND(CDECL);    ND(CVASGN);\n    ND(CVDECL);     ND(OP_ASGN1);   ND(OP_ASGN2); ND(OP_ASGN_AND);\n    ND(OP_ASGN_OR); ND(CALL);       ND(FCALL);    ND(VCALL);\n    ND(SUPER);      ND(ZSUPER);     ND(ARRAY);    ND(ZARRAY);\n    ND(HASH);       ND(RETURN);     ND(YIELD);    ND(LVAR);\n    ND(DVAR);       ND(GVAR);       ND(IVAR);     ND(CONST);\n    ND(CVAR);       ND(NTH_REF);    ND(BACK_REF); ND(MATCH);\n    ND(MATCH2);     ND(MATCH3);     ND(LIT);      ND(STR);\n    ND(DSTR);       ND(XSTR);       ND(DXSTR);    ND(EVSTR);\n    ND(DREGX);      ND(DREGX_ONCE); ND(ARGS);     ND(ARGSCAT);\n    ND(ARGSPUSH);   ND(SPLAT);      ND(TO_ARY);   ND(SVALUE);\n    ND(BLOCK_ARG);  ND(BLOCK_PASS); ND(DEFN);     ND(DEFS);\n    ND(ALIAS);      ND(VALIAS);     ND(UNDEF);    ND(CLASS);\n    ND(MODULE);     ND(SCLASS);     ND(COLON2);   ND(COLON3)\n    ND(CREF);       ND(DOT2);       ND(DOT3);     ND(FLIP2);\n    ND(FLIP3);      ND(ATTRSET);    ND(SELF);     ND(NIL);\n    ND(TRUE);       ND(FALSE);      ND(DEFINED);  ND(NEWLINE);\n    ND(POSTEXE);    ND(ALLOCA);     ND(DMETHOD);  ND(BMETHOD);\n    ND(MEMO);       ND(IFUNC);      ND(DSYM);     ND(ATTRASGN);\n    ND(LAST);\n    default:\n      return \"unknown\";\n  }\n}\n\nstatic inline void\nobj_dump_class(json_gen gen, VALUE obj)\n{\n  if (RBASIC(obj)->klass) {\n    json_gen_cstr(gen, \"class\");\n    json_gen_value(gen, RBASIC(obj)->klass);\n\n    VALUE name = rb_classname(RBASIC(obj)->klass);\n    if (RTEST(name)) {\n      json_gen_cstr(gen, \"class_name\");\n      json_gen_cstr(gen, RSTRING_PTR(name));\n    }\n  }\n}\n\n/* TODO\n *  look for FL_EXIVAR flag and print ivars\n *  print more detail about Proc/struct BLOCK in T_DATA if freefunc == blk_free\n *  add Memprof.dump_all for full heap dump\n *  print details on different types of nodes (nd_next, nd_lit, nd_nth, etc)\n */\n\nstatic void\nobj_dump(VALUE obj, json_gen gen)\n{\n  int type;\n  json_gen_map_open(gen);\n\n  json_gen_cstr(gen, \"_id\");\n  json_gen_value(gen, obj);\n\n  struct obj_track *tracker = NULL;\n  if (st_lookup(objs, (st_data_t)obj, (st_data_t *)&tracker) && BUILTIN_TYPE(obj) != T_NODE) {\n    json_gen_cstr(gen, \"file\");\n    json_gen_cstr(gen, tracker->source);\n    json_gen_cstr(gen, \"line\");\n    json_gen_integer(gen, tracker->line);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, (tracker->time[0].tv_sec * 1000000) + tracker->time[0].tv_usec);\n  }\n\n  json_gen_cstr(gen, \"type\");\n  switch (type=BUILTIN_TYPE(obj)) {\n    case T_DATA:\n      json_gen_cstr(gen, \"data\");\n      obj_dump_class(gen, obj);\n\n      if (DATA_PTR(obj)) {\n        json_gen_cstr(gen, \"data\");\n        json_gen_pointer(gen, DATA_PTR(obj));\n      }\n\n      if (RDATA(obj)->dfree == (RUBY_DATA_FUNC)rb_blk_free) {\n        void *val;\n        VALUE ptr;\n\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_body);\n        if (val) {\n          json_gen_cstr(gen, \"nd_body\");\n          json_gen_pointer(gen, val);\n        }\n\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_var);\n        if (val) {\n          json_gen_cstr(gen, \"nd_var\");\n          json_gen_pointer(gen, val);\n        }\n\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_cref);\n        if (val) {\n          json_gen_cstr(gen, \"nd_cref\");\n          json_gen_pointer(gen, val);\n        }\n\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_dyna_vars);\n        if (val) {\n          json_gen_cstr(gen, \"vars\");\n          json_gen_pointer(gen, val);\n        }\n\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_scope);\n        if (val) {\n          json_gen_cstr(gen, \"scope\");\n          json_gen_pointer(gen, val);\n        }\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_BLOCK_self);\n        json_gen_cstr(gen, \"self\");\n        json_gen_value(gen, ptr);\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_BLOCK_klass);\n        json_gen_cstr(gen, \"klass\");\n        json_gen_value(gen, ptr);\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_BLOCK_orig_thread);\n        json_gen_cstr(gen, \"thread\");\n        json_gen_value(gen, ptr);\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_BLOCK_wrapper);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"wrapper\");\n          json_gen_value(gen, ptr);\n        }\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_BLOCK_block_obj);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"block\");\n          json_gen_value(gen, ptr);\n        }\n\n        /* TODO: is .prev actually useful? refers to non-heap allocated struct BLOCKs,\n         * but we don't print out any information about those\n         */\n        /*\n        json_gen_cstr(gen, \"prev\");\n        json_gen_array_open(gen);\n        val = *(void**)(DATA_PTR(obj) + memprof_config.offset_BLOCK_prev);\n        while (val) {\n          json_gen_pointer(gen, val);\n          prev = val;\n          val = *(void**)(ptr + memprof_config.offset_BLOCK_prev);\n          if (prev == val)\n            break;\n        }\n        json_gen_array_close(gen);\n        */\n\n      } else if (RDATA(obj)->dmark == (RUBY_DATA_FUNC)rb_bm_mark) {\n        VALUE ptr;\n        ID id, mid;\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_METHOD_klass);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"klass\");\n          json_gen_value(gen, ptr);\n        }\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_METHOD_rklass);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"rklass\");\n          json_gen_value(gen, ptr);\n        }\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_METHOD_recv);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"recv\");\n          json_gen_value(gen, ptr);\n        }\n\n        ptr = *(VALUE*)(DATA_PTR(obj) + memprof_config.offset_METHOD_body);\n        if (RTEST(ptr)) {\n          json_gen_cstr(gen, \"node\");\n          json_gen_value(gen, ptr);\n        }\n\n        mid = *(ID*)(DATA_PTR(obj) + memprof_config.offset_METHOD_id);\n        if (mid) {\n          json_gen_cstr(gen, \"mid\");\n          json_gen_id(gen, mid);\n        }\n\n        id = *(ID*)(DATA_PTR(obj) + memprof_config.offset_METHOD_oid);\n        if (id && id != mid) {\n          json_gen_cstr(gen, \"oid\");\n          json_gen_id(gen, id);\n        }\n\n      } else if (RDATA(obj)->dmark == (RUBY_DATA_FUNC)rb_thread_mark) {\n        rb_thread_t th = (rb_thread_t)DATA_PTR(obj);\n\n        if (th == rb_curr_thread) {\n          json_gen_cstr(gen, \"current\");\n          json_gen_bool(gen, 1);\n        } else {\n          if (th->dyna_vars) {\n            json_gen_cstr(gen, \"varmap\");\n            json_gen_pointer(gen, th->dyna_vars);\n          }\n\n          json_gen_cstr(gen, \"node\");\n          json_gen_pointer(gen, th->node);\n\n          json_gen_cstr(gen, \"cref\");\n          json_gen_pointer(gen, th->cref);\n\n          char *status;\n          switch (th->status) {\n            case THREAD_TO_KILL:\n              status = \"to_kill\";\n              break;\n            case THREAD_RUNNABLE:\n              status = \"runnable\";\n              break;\n            case THREAD_STOPPED:\n              status = \"stopped\";\n              break;\n            case THREAD_KILLED:\n              status = \"killed\";\n              break;\n            default:\n              status = \"unknown\";\n          }\n\n          json_gen_cstr(gen, \"status\");\n          json_gen_cstr(gen, status);\n\n          #define WAIT_FD\t\t(1<<0)\n          #define WAIT_SELECT\t(1<<1)\n          #define WAIT_TIME\t(1<<2)\n          #define WAIT_JOIN\t(1<<3)\n          #define WAIT_PID\t(1<<4)\n\n          json_gen_cstr(gen, \"wait_for\");\n          json_gen_array_open(gen);\n          if (th->wait_for & WAIT_FD)\n            json_gen_cstr(gen, \"fd\");\n          if (th->wait_for & WAIT_SELECT)\n            json_gen_cstr(gen, \"select\");\n          if (th->wait_for & WAIT_TIME)\n            json_gen_cstr(gen, \"time\");\n          if (th->wait_for & WAIT_JOIN)\n            json_gen_cstr(gen, \"join\");\n          if (th->wait_for & WAIT_PID)\n            json_gen_cstr(gen, \"pid\");\n          json_gen_array_close(gen);\n\n          if (th->wait_for & WAIT_FD) {\n            json_gen_cstr(gen, \"fd\");\n            json_gen_integer(gen, th->fd);\n          }\n\n          #define DELAY_INFTY 1E30\n\n          if (th->wait_for & WAIT_TIME) {\n            json_gen_cstr(gen, \"delay\");\n            if (th->delay == DELAY_INFTY)\n              json_gen_cstr(gen, \"infinity\");\n            else\n              json_gen_double(gen, th->delay - timeofday());\n          }\n\n          if (th->wait_for & WAIT_JOIN) {\n            json_gen_cstr(gen, \"join\");\n            json_gen_value(gen, th->join->thread);\n          }\n        }\n\n        json_gen_cstr(gen, \"priority\");\n        json_gen_integer(gen, th->priority);\n\n        if (th == rb_main_thread) {\n          json_gen_cstr(gen, \"main\");\n          json_gen_bool(gen, 1);\n        }\n\n        if (th->next && th->next != rb_main_thread) {\n          json_gen_cstr(gen, \"next\");\n          json_gen_value(gen, th->next->thread);\n        }\n        if (th->prev && th->prev != th && (th->prev == rb_main_thread || th->prev != th->next)) {\n          json_gen_cstr(gen, \"prev\");\n          json_gen_value(gen, th->prev->thread);\n        }\n\n        if (th->locals) {\n          json_gen_cstr(gen, \"variables\");\n          json_gen_map_open(gen);\n          st_foreach(th->locals, each_ivar, (st_data_t)gen);\n          json_gen_map_close(gen);\n        }\n\n      }\n      break;\n\n    case T_STRUCT:\n      json_gen_cstr(gen, \"struct\");\n      obj_dump_class(gen, obj);\n      break;\n\n    case T_FILE:\n      json_gen_cstr(gen, \"file\");\n      obj_dump_class(gen, obj);\n\n      OpenFile *file = RFILE(obj)->fptr;\n\n      if (file->f) {\n        json_gen_cstr(gen, \"fileno\");\n        json_gen_integer(gen, fileno(file->f));\n      }\n\n      if (file->f2) {\n        json_gen_cstr(gen, \"fileno2\");\n        json_gen_integer(gen, fileno(file->f2));\n      }\n\n      if (file->pid) {\n        json_gen_cstr(gen, \"pid\");\n        json_gen_integer(gen, file->pid);\n      }\n\n      if (file->path) {\n        json_gen_cstr(gen, \"path\");\n        json_gen_cstr(gen, file->path);\n      }\n\n      if (file->mode) {\n        json_gen_cstr(gen, \"mode\");\n        json_gen_array_open(gen);\n        if (file->mode & FMODE_READABLE)\n          json_gen_cstr(gen, \"readable\");\n        if (file->mode & FMODE_WRITABLE)\n          json_gen_cstr(gen, \"writable\");\n        if (file->mode & FMODE_READWRITE)\n          json_gen_cstr(gen, \"readwrite\");\n        if (file->mode & FMODE_APPEND)\n          json_gen_cstr(gen, \"append\");\n        if (file->mode & FMODE_CREATE)\n          json_gen_cstr(gen, \"create\");\n        if (file->mode & FMODE_BINMODE)\n          json_gen_cstr(gen, \"binmode\");\n        if (file->mode & FMODE_SYNC)\n          json_gen_cstr(gen, \"sync\");\n        if (file->mode & FMODE_WBUF)\n          json_gen_cstr(gen, \"wbuf\");\n        if (file->mode & FMODE_RBUF)\n          json_gen_cstr(gen, \"rbuf\");\n        if (file->mode & FMODE_WSPLIT)\n          json_gen_cstr(gen, \"wsplit\");\n        if (file->mode & FMODE_WSPLIT_INITIALIZED)\n          json_gen_cstr(gen, \"wsplit_initialized\");\n        json_gen_array_close(gen);\n      }\n\n      break;\n\n    case T_FLOAT:\n      json_gen_cstr(gen, \"float\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"data\");\n      json_gen_double(gen, RFLOAT(obj)->value);\n      break;\n\n    case T_BIGNUM:\n      json_gen_cstr(gen, \"bignum\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"negative\");\n      json_gen_bool(gen, RBIGNUM(obj)->sign == 0);\n\n      json_gen_cstr(gen, \"length\");\n      json_gen_integer(gen, RBIGNUM(obj)->len);\n\n      json_gen_cstr(gen, \"data\");\n      json_gen_string(gen, RBIGNUM(obj)->digits, RBIGNUM(obj)->len);\n      break;\n\n    case T_MATCH:\n      json_gen_cstr(gen, \"match\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"data\");\n      json_gen_value(gen, RMATCH(obj)->str);\n      break;\n\n    case T_REGEXP:\n      json_gen_cstr(gen, \"regexp\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"length\");\n      json_gen_integer(gen, RREGEXP(obj)->len);\n\n      json_gen_cstr(gen, \"data\");\n      json_gen_cstr(gen, RREGEXP(obj)->str);\n      break;\n\n    case T_SCOPE:\n      json_gen_cstr(gen, \"scope\");\n\n      struct SCOPE *scope = (struct SCOPE *)obj;\n      if (scope->local_tbl) {\n        int i = 0;\n        int n = scope->local_tbl[0];\n        VALUE *list = &scope->local_vars[-1];\n        VALUE cur = *list++;\n\n        if (RTEST(cur)) {\n          json_gen_cstr(gen, \"node\");\n          json_gen_value(gen, cur);\n        }\n\n        if (n) {\n          json_gen_cstr(gen, \"variables\");\n          json_gen_map_open(gen);\n          while (n--) {\n            cur = *list++;\n            i++;\n\n            if (!rb_is_local_id(scope->local_tbl[i]))\n              continue;\n\n            json_gen_id(gen, scope->local_tbl[i]);\n            json_gen_value(gen, cur);\n          }\n          json_gen_map_close(gen);\n        }\n      }\n      break;\n\n    case T_NODE:\n      json_gen_cstr(gen, \"node\");\n\n      json_gen_cstr(gen, \"node_type\");\n      json_gen_cstr(gen, nd_type_str(obj));\n\n      json_gen_cstr(gen, \"file\");\n      json_gen_cstr(gen, RNODE(obj)->nd_file);\n\n      json_gen_cstr(gen, \"line\");\n      json_gen_integer(gen, nd_line(obj));\n\n      json_gen_cstr(gen, \"node_code\");\n      json_gen_integer(gen, nd_type(obj));\n\n      #define PRINT_ID(sub) json_gen_id(gen, RNODE(obj)->sub.id)\n      #define PRINT_VAL(sub) json_gen_value(gen, RNODE(obj)->sub.value)\n\n      int nd_type = nd_type(obj);\n      json_gen_cstr(gen, \"n1\");\n      switch(nd_type) {\n        case NODE_LVAR:\n        case NODE_DVAR:\n        case NODE_IVAR:\n        case NODE_CVAR:\n        case NODE_GVAR:\n        case NODE_CONST:\n        case NODE_ATTRSET:\n        case NODE_LASGN:\n        case NODE_IASGN:\n        case NODE_DASGN:\n        case NODE_CVASGN:\n        case NODE_CVDECL:\n        case NODE_GASGN:\n        case NODE_DASGN_CURR:\n        case NODE_BLOCK_ARG:\n        case NODE_CDECL:\n        case NODE_VALIAS:\n          PRINT_ID(u1);\n          break;\n\n        case NODE_OP_ASGN2:\n          if (RNODE(obj)->u3.id > 1000000)\n            PRINT_VAL(u1);\n          else\n            PRINT_ID(u1);\n          break;\n\n        case NODE_SCOPE: {\n          ID *tbl = RNODE(obj)->nd_tbl;\n          json_gen_array_open(gen);\n          if (tbl) {\n            int size = tbl[0];\n            int i = 3;\n\n            for (; i < size+1; i++) {\n              json_gen_id(gen, tbl[i]);\n            }\n          }\n          json_gen_array_close(gen);\n          break;\n        }\n\n        case NODE_IFUNC:\n        case NODE_CFUNC: {\n          const char *name = bin_find_symbol_name((void*)RNODE(obj)->u1.value);\n          json_gen_format(gen, \"0x%x: %s\", RNODE(obj)->u1.value, name ? name : \"???\");\n          break;\n        }\n\n        default:\n          PRINT_VAL(u1);\n      }\n\n      json_gen_cstr(gen, \"n2\");\n      switch(nd_type) {\n        case NODE_CALL:\n        case NODE_FBODY:\n        case NODE_DEFN:\n        case NODE_ATTRASGN:\n        case NODE_FCALL:\n        case NODE_VCALL:\n        case NODE_COLON2:\n        case NODE_COLON3:\n        case NODE_BACK_REF:\n        case NODE_DEFS:\n        case NODE_VALIAS:\n          PRINT_ID(u2);\n          break;\n\n        case NODE_OP_ASGN1:\n          if (RNODE(obj)->nd_mid == 0)\n            json_gen_cstr(gen, \":||\");\n          else if (RNODE(obj)->nd_mid == 1)\n            json_gen_cstr(gen, \":&&\");\n          else\n            PRINT_ID(u2);\n          break;\n\n        case NODE_OP_ASGN2:\n          if (RNODE(obj)->u3.id > 1000000) {\n            PRINT_VAL(u2);\n          } else {\n            if (RNODE(obj)->nd_mid == 0)\n              json_gen_cstr(gen, \":||\");\n            else if (RNODE(obj)->nd_mid == 1)\n              json_gen_cstr(gen, \":&&\");\n            else\n              PRINT_ID(u2);\n          }\n          break;\n\n        case NODE_DREGX:\n        case NODE_DREGX_ONCE:\n        case NODE_NTH_REF:\n        case NODE_IFUNC:\n        case NODE_CFUNC:\n        case NODE_NEWLINE:\n          json_gen_integer(gen, RNODE(obj)->u2.argc);\n          break;\n\n        case NODE_BLOCK:\n        case NODE_ARRAY:\n          if (RNODE(obj)->u2.node == RNODE(obj))\n            json_gen_null(gen);\n          else\n            PRINT_VAL(u2);\n          break;\n\n        default:\n          PRINT_VAL(u2);\n      }\n\n      json_gen_cstr(gen, \"n3\");\n      switch(nd_type) {\n        case NODE_ARGS:\n          json_gen_integer(gen, RNODE(obj)->u3.cnt);\n          break;\n\n        case NODE_OP_ASGN2:\n          if (RNODE(obj)->u3.id > 1000000)\n            PRINT_VAL(u3);\n          else\n            PRINT_ID(u3);\n          break;\n\n        default:\n          PRINT_VAL(u3);\n      }\n      break;\n\n    case T_STRING:\n      json_gen_cstr(gen, \"string\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"length\");\n      json_gen_integer(gen, RSTRING_LEN(obj));\n\n      if (FL_TEST(obj, ELTS_SHARED|FL_USER3)) {\n        json_gen_cstr(gen, \"shared\");\n        json_gen_value(gen, RSTRING(obj)->aux.shared);\n\n        json_gen_cstr(gen, \"flags\");\n        json_gen_array_open(gen);\n        if (FL_TEST(obj, ELTS_SHARED))\n          json_gen_cstr(gen, \"elts_shared\");\n        if (FL_TEST(obj, FL_USER3))\n          json_gen_cstr(gen, \"str_assoc\");\n        json_gen_array_close(gen);\n      } else {\n        json_gen_cstr(gen, \"data\");\n        json_gen_string(gen, (unsigned char *)RSTRING_PTR(obj), RSTRING_LEN(obj));\n      }\n      break;\n\n    case T_VARMAP:\n      json_gen_cstr(gen, \"varmap\");\n      obj_dump_class(gen, obj);\n\n      struct RVarmap *vars = (struct RVarmap *)obj;\n\n      if (vars->next) {\n        json_gen_cstr(gen, \"next\");\n        json_gen_value(gen, (VALUE)vars->next);\n      }\n\n      if (vars->id) {\n        json_gen_cstr(gen, \"data\");\n        json_gen_map_open(gen);\n        json_gen_id(gen, vars->id);\n        json_gen_value(gen, vars->val);\n        json_gen_map_close(gen);\n      }\n      break;\n\n    case T_CLASS:\n    case T_MODULE:\n    case T_ICLASS:\n      json_gen_cstr(gen, type==T_CLASS ? \"class\" : type==T_MODULE ? \"module\" : \"iclass\");\n      obj_dump_class(gen, obj);\n\n      json_gen_cstr(gen, \"name\");\n      VALUE name = rb_classname(obj);\n      if (RTEST(name))\n        json_gen_cstr(gen, RSTRING_PTR(name));\n      else\n        json_gen_cstr(gen, 0);\n\n      json_gen_cstr(gen, \"super\");\n      json_gen_value(gen, RCLASS(obj)->super);\n\n      if (RTEST(RCLASS(obj)->super)) {\n        json_gen_cstr(gen, \"super_name\");\n        VALUE super_name = rb_classname(RCLASS(obj)->super);\n        if (RTEST(super_name))\n          json_gen_cstr(gen, RSTRING_PTR(super_name));\n        else\n          json_gen_cstr(gen, 0);\n      }\n\n      if (FL_TEST(obj, FL_SINGLETON)) {\n        json_gen_cstr(gen, \"singleton\");\n        json_gen_bool(gen, 1);\n      }\n\n      if (RCLASS(obj)->iv_tbl && RCLASS(obj)->iv_tbl->num_entries) {\n        json_gen_cstr(gen, \"ivars\");\n        json_gen_map_open(gen);\n        st_foreach(RCLASS(obj)->iv_tbl, each_ivar, (st_data_t)gen);\n        json_gen_map_close(gen);\n      }\n\n      if (RCLASS(obj)->m_tbl && RCLASS(obj)->m_tbl->num_entries) {\n        json_gen_cstr(gen, \"methods\");\n        json_gen_map_open(gen);\n        st_foreach(RCLASS(obj)->m_tbl, each_ivar, (st_data_t)gen);\n        json_gen_map_close(gen);\n      }\n      break;\n\n    case T_OBJECT:\n      json_gen_cstr(gen, \"object\");\n      obj_dump_class(gen, obj);\n\n      struct RClass *klass = RCLASS(obj);\n\n      if (klass->iv_tbl && klass->iv_tbl->num_entries) {\n        json_gen_cstr(gen, \"ivars\");\n        json_gen_map_open(gen);\n        st_foreach(klass->iv_tbl, each_ivar, (st_data_t)gen);\n        json_gen_map_close(gen);\n      }\n      break;\n\n    case T_ARRAY:\n      json_gen_cstr(gen, \"array\");\n      obj_dump_class(gen, obj);\n\n      struct RArray *ary = RARRAY(obj);\n\n      json_gen_cstr(gen, \"length\");\n      json_gen_integer(gen, ary->len);\n\n      if (FL_TEST(obj, ELTS_SHARED)) {\n        json_gen_cstr(gen, \"shared\");\n        json_gen_value(gen, ary->aux.shared);\n      } else if (ary->len) {\n        json_gen_cstr(gen, \"data\");\n        json_gen_array_open(gen);\n        int i;\n        for(i=0; i < ary->len; i++)\n          json_gen_value(gen, ary->ptr[i]);\n        json_gen_array_close(gen);\n      }\n      break;\n\n    case T_HASH:\n      json_gen_cstr(gen, \"hash\");\n      obj_dump_class(gen, obj);\n\n      struct RHash *hash = RHASH(obj);\n\n      json_gen_cstr(gen, \"length\");\n      if (hash->tbl)\n        json_gen_integer(gen, hash->tbl->num_entries);\n      else\n        json_gen_integer(gen, 0);\n\n      json_gen_cstr(gen, \"default\");\n      json_gen_value(gen, hash->ifnone);\n\n      if (hash->tbl && hash->tbl->num_entries) {\n        json_gen_cstr(gen, \"data\");\n        //json_gen_map_open(gen);\n        json_gen_array_open(gen);\n        st_foreach(hash->tbl, each_hash_entry, (st_data_t)gen);\n        json_gen_array_close(gen);\n        //json_gen_map_close(gen);\n      }\n      break;\n\n    default:\n      json_gen_cstr(gen, \"unknown\");\n      obj_dump_class(gen, obj);\n  }\n\n  json_gen_cstr(gen, \"code\");\n  json_gen_integer(gen, BUILTIN_TYPE(obj));\n\n  json_gen_map_close(gen);\n}\n\nextern st_table *rb_global_tbl;\n\nstatic int\nglobals_each_dump(st_data_t key, st_data_t record, st_data_t arg)\n{\n  json_gen_id((json_gen)arg, (ID)key);\n  json_gen_value((json_gen)arg, rb_gvar_get((void*)record));\n  return ST_CONTINUE;\n}\n\nstatic int\nfinalizers_each_dump(st_data_t key, st_data_t val, st_data_t arg)\n{\n  json_gen gen = (json_gen)arg;\n  json_gen_array_open(gen);\n  json_gen_value(gen, (VALUE)key);\n  json_gen_value(gen, (VALUE)val);\n  json_gen_array_close(gen);\n  return ST_CONTINUE;\n}\n\nstatic void\nmemprof_dump_globals(json_gen gen)\n{\n  json_gen_map_open(gen);\n\n  json_gen_cstr(gen, \"_id\");\n  json_gen_cstr(gen, \"globals\");\n\n  json_gen_cstr(gen, \"type\");\n  json_gen_cstr(gen, \"globals\");\n\n  json_gen_cstr(gen, \"variables\");\n\n  json_gen_map_open(gen);\n  st_foreach(rb_global_tbl, globals_each_dump, (st_data_t)gen);\n  json_gen_map_close(gen);\n\n  json_gen_map_close(gen);\n  json_gen_reset(gen);\n}\n\nstatic void\nmemprof_dump_stack_frame(json_gen gen, struct FRAME *frame)\n{\n  json_gen_map_open(gen);\n\n  json_gen_cstr(gen, \"_id\");\n  json_gen_pointer(gen, frame);\n\n  json_gen_cstr(gen, \"type\");\n  json_gen_cstr(gen, \"frame\");\n\n  json_gen_cstr(gen, \"self\");\n  json_gen_value(gen, frame->self);\n\n  if (frame->last_class) {\n    json_gen_cstr(gen, \"last_class\");\n    json_gen_value(gen, frame->last_class);\n  }\n\n  if (frame->orig_func) {\n    json_gen_cstr(gen, \"orig_func\");\n    json_gen_id(gen, frame->orig_func);\n  }\n\n  if (frame->last_func && frame->last_func != frame->orig_func) {\n    json_gen_cstr(gen, \"last_func\");\n    json_gen_id(gen, frame->last_func);\n  }\n\n  if (frame->node) {\n    json_gen_cstr(gen, \"node\");\n    json_gen_pointer(gen, (void*)frame->node);\n  }\n\n  if (frame->prev) {\n    json_gen_cstr(gen, \"prev\");\n    json_gen_pointer(gen, (void*)frame->prev);\n  }\n\n  if (frame->tmp) {\n    json_gen_cstr(gen, \"tmp\");\n    json_gen_pointer(gen, (void*)frame->tmp);\n  }\n\n  json_gen_map_close(gen);\n  json_gen_reset(gen);\n\n  if (frame->prev) {\n    memprof_dump_stack_frame(gen, frame->prev);\n  }\n}\n\nstatic void\nmemprof_dump_stack(json_gen gen)\n{\n  memprof_dump_stack_frame(gen, ruby_frame);\n}\n\nstatic void\nmemprof_dump_lsof(json_gen gen)\n{\n  VALUE cmd = rb_str_new2(\"lsof -np \");\n  VALUE pid = rb_funcall(rb_mProcess, rb_intern(\"pid\"), 0);\n  rb_str_append(cmd, rb_funcall(pid, rb_intern(\"to_s\"), 0));\n\n  VALUE lsof = rb_funcall(rb_cObject, '`', 1, cmd);\n  if (RTEST(lsof)) {\n    VALUE newline = rb_str_new2(\"\\n\");\n    VALUE lines = rb_funcall(lsof, rb_intern(\"split\"), 1, newline);\n    int i;\n    for (i=1; i < RARRAY_LEN(lines); i++) {\n      VALUE parts = rb_funcall(RARRAY_PTR(lines)[i], rb_intern(\"split\"), 2, Qnil, INT2FIX(9));\n\n      json_gen_map_open(gen);\n\n      json_gen_cstr(gen, \"_id\");\n      json_gen_format(gen, \"lsof:%d\", i);\n\n      json_gen_cstr(gen, \"type\");\n      json_gen_cstr(gen, \"lsof\");\n\n      json_gen_cstr(gen, \"fd\");\n      json_gen_cstr(gen, RSTRING_PTR(RARRAY_PTR(parts)[3]));\n\n      json_gen_cstr(gen, \"fd_type\");\n      json_gen_cstr(gen, RSTRING_PTR(RARRAY_PTR(parts)[4]));\n\n      json_gen_cstr(gen, \"fd_name\");\n      json_gen_cstr(gen, RSTRING_PTR(RARRAY_PTR(parts)[RARRAY_LEN(parts)-1]));\n\n      json_gen_map_close(gen);\n      json_gen_reset(gen);\n    }\n  }\n}\n\nstatic void\nmemprof_dump_ps(json_gen gen)\n{\n  VALUE cmd = rb_str_new2(\"ps -o rss,vsize -p \");\n  VALUE pid = rb_funcall(rb_mProcess, rb_intern(\"pid\"), 0);\n  rb_str_append(cmd, rb_funcall(pid, rb_intern(\"to_s\"), 0));\n\n  VALUE ps = rb_funcall(rb_cObject, '`', 1, cmd);\n  if (RTEST(ps)) {\n    VALUE newline = rb_str_new2(\"\\n\");\n    VALUE lines = rb_funcall(ps, rb_intern(\"split\"), 1, newline);\n\n    if (RARRAY_LEN(lines) == 2) {\n      VALUE parts = rb_funcall(RARRAY_PTR(lines)[1], rb_intern(\"split\"), 0);\n\n      json_gen_map_open(gen);\n\n      json_gen_cstr(gen, \"_id\");\n      json_gen_cstr(gen, \"ps\");\n\n      json_gen_cstr(gen, \"type\");\n      json_gen_cstr(gen, \"ps\");\n\n      json_gen_cstr(gen, \"rss\");\n      json_gen_cstr(gen, RSTRING_PTR(RARRAY_PTR(parts)[0]));\n\n      json_gen_cstr(gen, \"vsize\");\n      json_gen_cstr(gen, RSTRING_PTR(RARRAY_PTR(parts)[1]));\n\n      json_gen_map_close(gen);\n      json_gen_reset(gen);\n    }\n  }\n}\n\nstatic void\nmemprof_dump_finalizers(json_gen gen)\n{\n  st_table *finalizer_table = *(st_table **)memprof_config.finalizer_table;\n  if (finalizer_table) {\n    json_gen_map_open(gen);\n\n    json_gen_cstr(gen, \"_id\");\n    json_gen_cstr(gen, \"finalizers\");\n\n    json_gen_cstr(gen, \"type\");\n    json_gen_cstr(gen, \"finalizers\");\n\n    json_gen_cstr(gen, \"data\");\n    json_gen_array_open(gen);\n    st_foreach(finalizer_table, finalizers_each_dump, (st_data_t)gen);\n    json_gen_array_close(gen);\n\n    json_gen_map_close(gen);\n    json_gen_reset(gen);\n  }\n}\n\nstatic int\nobjs_each_dump(st_data_t key, st_data_t record, st_data_t arg)\n{\n  obj_dump((VALUE)key, (json_gen)arg);\n  json_gen_reset((json_gen)arg);\n  return ST_CONTINUE;\n}\n\nstatic VALUE\nmemprof_dump(int argc, VALUE *argv, VALUE self)\n{\n  VALUE ret = Qnil;\n  int old = track_objs;\n\n  if (rb_block_given_p()) {\n    memprof_start(self);\n    ret = rb_yield(Qnil);\n  } else if (!track_objs)\n    rb_raise(rb_eRuntimeError, \"object tracking disabled, call Memprof.start first\");\n\n  track_objs = 0;\n\n  json_gen gen = json_for_args(argc, argv);\n  st_foreach(objs, objs_each_dump, (st_data_t)gen);\n  json_free(gen);\n\n  if (rb_block_given_p())\n    memprof_stop(self);\n  track_objs = old;\n\n  return ret;\n}\n\nstatic VALUE\nmemprof_dump_all(int argc, VALUE *argv, VALUE self)\n{\n  if (memprof_config.heaps == NULL ||\n      memprof_config.heaps_used == NULL ||\n      memprof_config.sizeof_RVALUE == 0 ||\n      memprof_config.sizeof_heaps_slot == 0 ||\n      memprof_config.offset_heaps_slot_slot == SIZE_MAX ||\n      memprof_config.offset_heaps_slot_limit == SIZE_MAX)\n    rb_raise(eUnsupported, \"not enough config data to dump heap\");\n\n  char *heaps = *(char**)memprof_config.heaps;\n  int heaps_used = *(int*)memprof_config.heaps_used;\n\n  char *p, *pend;\n  int i, limit;\n  VALUE str;\n  char *filename = NULL;\n  char *in_progress_filename = NULL;\n  FILE *out = NULL;\n\n  rb_scan_args(argc, argv, \"01\", &str);\n\n  if (RTEST(str)) {\n    filename = StringValueCStr(str);\n    size_t filename_len = strlen(filename);\n    in_progress_filename = alloca(filename_len + 13);\n    memcpy(in_progress_filename, filename, filename_len);\n    memcpy(in_progress_filename + filename_len, \".IN_PROGRESS\\0\", 13);\n\n    out = fopen(in_progress_filename, \"w\");\n    if (!out)\n      rb_raise(rb_eArgError, \"unable to open output file\");\n  }\n\n  json_gen_config conf = { .beautify = 0, .indentString = \"  \" };\n  json_gen gen = json_gen_alloc2((json_print_t)&json_print, &conf, NULL, (void*)out);\n\n  track_objs = 0;\n\n  memprof_dump_finalizers(gen);\n  memprof_dump_globals(gen);\n  memprof_dump_stack(gen);\n\n  for (i=0; i < heaps_used; i++) {\n    p = *(char**)(heaps + (i * memprof_config.sizeof_heaps_slot) + memprof_config.offset_heaps_slot_slot);\n    limit = *(int*)(heaps + (i * memprof_config.sizeof_heaps_slot) + memprof_config.offset_heaps_slot_limit);\n    pend = p + (memprof_config.sizeof_RVALUE * limit);\n\n    while (p < pend) {\n      if (RBASIC(p)->flags) {\n        obj_dump((VALUE)p, gen);\n        json_gen_reset(gen);\n      }\n\n      p += memprof_config.sizeof_RVALUE;\n    }\n  }\n\n  memprof_dump_lsof(gen);\n  memprof_dump_ps(gen);\n\n  json_gen_clear(gen);\n  json_gen_free(gen);\n\n  if (out) {\n    fclose(out);\n    rename(in_progress_filename, filename);\n  }\n\n  track_objs = 1;\n\n  return Qnil;\n}\n\nstatic void\ninit_memprof_config_base() {\n  memset(&memprof_config, 0, sizeof(memprof_config));\n  memprof_config.offset_heaps_slot_limit = SIZE_MAX;\n  memprof_config.offset_heaps_slot_slot = SIZE_MAX;\n  memprof_config.pagesize = getpagesize();\n  assert(memprof_config.pagesize);\n}\n\nstatic void\ninit_memprof_config_extended() {\n  /* If we don't have add_freelist, find the functions it gets inlined into */\n  memprof_config.add_freelist               = bin_find_symbol(\"add_freelist\", NULL, 0);\n\n  /*\n   * Sometimes gc_sweep gets inlined in garbage_collect\n   * (e.g., on REE it gets inlined into garbage_collect_0).\n   */\n  if (memprof_config.add_freelist == NULL) {\n    memprof_config.gc_sweep                 = bin_find_symbol(\"gc_sweep\",\n                                                &memprof_config.gc_sweep_size, 0);\n    if (memprof_config.gc_sweep == NULL)\n      memprof_config.gc_sweep               = bin_find_symbol(\"garbage_collect_0\",\n                                                &memprof_config.gc_sweep_size, 0);\n    if (memprof_config.gc_sweep == NULL)\n      memprof_config.gc_sweep               = bin_find_symbol(\"garbage_collect\",\n                                                &memprof_config.gc_sweep_size, 0);\n\n    memprof_config.finalize_list            = bin_find_symbol(\"finalize_list\",\n                                                &memprof_config.finalize_list_size, 0);\n    memprof_config.rb_gc_force_recycle      = bin_find_symbol(\"rb_gc_force_recycle\",\n                                                &memprof_config.rb_gc_force_recycle_size, 0);\n    memprof_config.freelist                 = bin_find_symbol(\"freelist\", NULL, 0);\n  }\n\n  memprof_config.classname                  = bin_find_symbol(\"classname\", NULL, 0);\n  memprof_config.bm_mark                    = bin_find_symbol(\"bm_mark\", NULL, 0);\n  memprof_config.blk_free                   = bin_find_symbol(\"blk_free\", NULL, 0);\n  memprof_config.thread_mark                = bin_find_symbol(\"thread_mark\", NULL, 0);\n  memprof_config.rb_mark_table_add_filename = bin_find_symbol(\"rb_mark_table_add_filename\", NULL, 0);\n\n  /* Stuff for dumping the heap */\n  memprof_config.heaps                      = bin_find_symbol(\"heaps\", NULL, 0);\n  memprof_config.heaps_used                 = bin_find_symbol(\"heaps_used\", NULL, 0);\n  memprof_config.finalizer_table            = bin_find_symbol(\"finalizer_table\", NULL, 0);\n\n#ifdef sizeof__RVALUE\n  memprof_config.sizeof_RVALUE              = sizeof__RVALUE;\n#else\n  memprof_config.sizeof_RVALUE              = bin_type_size(\"RVALUE\");\n#endif\n#ifdef sizeof__heaps_slot\n  memprof_config.sizeof_heaps_slot          = sizeof__heaps_slot;\n#else\n  memprof_config.sizeof_heaps_slot          = bin_type_size(\"heaps_slot\");\n#endif\n#ifdef offset__heaps_slot__limit\n  memprof_config.offset_heaps_slot_limit    = offset__heaps_slot__limit;\n#else\n  memprof_config.offset_heaps_slot_limit    = bin_type_member_offset(\"heaps_slot\", \"limit\");\n#endif\n#ifdef offset__heaps_slot__slot\n  memprof_config.offset_heaps_slot_slot     = offset__heaps_slot__slot;\n#else\n  memprof_config.offset_heaps_slot_slot     = bin_type_member_offset(\"heaps_slot\", \"slot\");\n#endif\n#ifdef offset__BLOCK__body\n  memprof_config.offset_BLOCK_body          = offset__BLOCK__body;\n#else\n  memprof_config.offset_BLOCK_body          = bin_type_member_offset(\"BLOCK\", \"body\");\n#endif\n#ifdef offset__BLOCK__var\n  memprof_config.offset_BLOCK_var           = offset__BLOCK__var;\n#else\n  memprof_config.offset_BLOCK_var           = bin_type_member_offset(\"BLOCK\", \"var\");\n#endif\n#ifdef offset__BLOCK__cref\n  memprof_config.offset_BLOCK_cref          = offset__BLOCK__cref;\n#else\n  memprof_config.offset_BLOCK_cref          = bin_type_member_offset(\"BLOCK\", \"cref\");\n#endif\n#ifdef offset__BLOCK__prev\n  memprof_config.offset_BLOCK_prev          = offset__BLOCK__prev;\n#else\n  memprof_config.offset_BLOCK_prev          = bin_type_member_offset(\"BLOCK\", \"prev\");\n#endif\n#ifdef offset__BLOCK__self\n  memprof_config.offset_BLOCK_self          = offset__BLOCK__self;\n#else\n  memprof_config.offset_BLOCK_self          = bin_type_member_offset(\"BLOCK\", \"self\");\n#endif\n#ifdef offset__BLOCK__klass\n  memprof_config.offset_BLOCK_klass         = offset__BLOCK__klass;\n#else\n  memprof_config.offset_BLOCK_klass         = bin_type_member_offset(\"BLOCK\", \"klass\");\n#endif\n#ifdef offset__BLOCK__orig_thread\n  memprof_config.offset_BLOCK_orig_thread   = offset__BLOCK__orig_thread;\n#else\n  memprof_config.offset_BLOCK_orig_thread   = bin_type_member_offset(\"BLOCK\", \"orig_thread\");\n#endif\n#ifdef offset__BLOCK__wrapper\n  memprof_config.offset_BLOCK_wrapper       = offset__BLOCK__wrapper;\n#else\n  memprof_config.offset_BLOCK_wrapper       = bin_type_member_offset(\"BLOCK\", \"wrapper\");\n#endif\n#ifdef offset__BLOCK__block_obj\n  memprof_config.offset_BLOCK_block_obj     = offset__BLOCK__block_obj;\n#else\n  memprof_config.offset_BLOCK_block_obj     = bin_type_member_offset(\"BLOCK\", \"block_obj\");\n#endif\n#ifdef offset__BLOCK__scope\n  memprof_config.offset_BLOCK_scope         = offset__BLOCK__scope;\n#else\n  memprof_config.offset_BLOCK_scope         = bin_type_member_offset(\"BLOCK\", \"scope\");\n#endif\n#ifdef offset__BLOCK__dyna_vars\n  memprof_config.offset_BLOCK_dyna_vars     = offset__BLOCK__dyna_vars;\n#else\n  memprof_config.offset_BLOCK_dyna_vars     = bin_type_member_offset(\"BLOCK\", \"dyna_vars\");\n#endif\n#ifdef offset__METHOD__klass\n  memprof_config.offset_METHOD_klass        = offset__METHOD__klass;\n#else\n  memprof_config.offset_METHOD_klass        = bin_type_member_offset(\"METHOD\", \"klass\");\n#endif\n#ifdef offset__METHOD__rklass\n  memprof_config.offset_METHOD_rklass       = offset__METHOD__rklass;\n#else\n  memprof_config.offset_METHOD_rklass       = bin_type_member_offset(\"METHOD\", \"rklass\");\n#endif\n#ifdef offset__METHOD__recv\n  memprof_config.offset_METHOD_recv         = offset__METHOD__recv;\n#else\n  memprof_config.offset_METHOD_recv         = bin_type_member_offset(\"METHOD\", \"recv\");\n#endif\n#ifdef offset__METHOD__id\n  memprof_config.offset_METHOD_id           = offset__METHOD__id;\n#else\n  memprof_config.offset_METHOD_id           = bin_type_member_offset(\"METHOD\", \"id\");\n#endif\n#ifdef offset__METHOD__oid\n  memprof_config.offset_METHOD_oid          = offset__METHOD__oid;\n#else\n  memprof_config.offset_METHOD_oid          = bin_type_member_offset(\"METHOD\", \"oid\");\n#endif\n#ifdef offset__METHOD__body\n  memprof_config.offset_METHOD_body         = offset__METHOD__body;\n#else\n  memprof_config.offset_METHOD_body         = bin_type_member_offset(\"METHOD\", \"body\");\n#endif\n\n  int heap_errors_printed = 0;\n\n  if (memprof_config.heaps == NULL)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to locate heaps\\n\");\n  if (memprof_config.heaps_used == NULL)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to locate heaps_used\\n\");\n  if (memprof_config.sizeof_RVALUE == 0)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to determine sizeof(RVALUE)\\n\");\n  if (memprof_config.sizeof_heaps_slot == 0)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to determine sizeof(heaps_slot)\\n\");\n  if (memprof_config.offset_heaps_slot_limit == SIZE_MAX)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to determine offset of heaps_slot->limit\\n\");\n  if (memprof_config.offset_heaps_slot_slot == SIZE_MAX)\n    heap_errors_printed += fprintf(stderr,\n      \"Failed to determine offset of heaps_slot->slot\\n\");\n\n  if (heap_errors_printed)\n    fprintf(stderr, \"You won't be able to dump your heap!\\n\");\n\n  int errors_printed = 0;\n\n  /* If we can't find add_freelist, we need to make sure we located the functions that it gets inlined into. */\n  if (memprof_config.add_freelist == NULL) {\n    if (memprof_config.gc_sweep == NULL) {\n      errors_printed += fprintf(stderr,\n        \"Failed to locate add_freelist (it's probably inlined, but we couldn't find it there either!)\\n\");\n      errors_printed += fprintf(stderr,\n        \"Failed to locate gc_sweep, garbage_collect_0, or garbage_collect\\n\");\n    }\n    if (memprof_config.gc_sweep_size == 0)\n      errors_printed += fprintf(stderr,\n        \"Failed to determine the size of gc_sweep/garbage_collect_0/garbage_collect: %zd\\n\",\n        memprof_config.gc_sweep_size);\n    if (memprof_config.finalize_list == NULL)\n      errors_printed += fprintf(stderr,\n        \"Failed to locate finalize_list\\n\");\n    if (memprof_config.finalize_list_size == 0)\n      errors_printed += fprintf(stderr,\n        \"Failed to determine the size of finalize_list: %zd\\n\",\n        memprof_config.finalize_list_size);\n    if (memprof_config.rb_gc_force_recycle == NULL)\n      errors_printed += fprintf(stderr,\n        \"Failed to locate rb_gc_force_recycle\\n\");\n    if (memprof_config.rb_gc_force_recycle_size == 0)\n      errors_printed += fprintf(stderr,\n        \"Failed to determine the size of rb_gc_force_recycle: %zd\\n\",\n        memprof_config.rb_gc_force_recycle_size);\n    if (memprof_config.freelist == NULL)\n      errors_printed += fprintf(stderr,\n        \"Failed to locate freelist\\n\");\n  }\n\n  if (memprof_config.classname == NULL)\n    errors_printed += fprintf(stderr,\n      \"Failed to locate classname\\n\");\n\n  if (errors_printed) {\n    VALUE ruby_build_info = rb_eval_string(\"require 'rbconfig'; RUBY_DESCRIPTION + '\\n' + RbConfig::CONFIG['CFLAGS'];\");\n    /* who knows what could happen */\n    if (TYPE(ruby_build_info) == T_STRING)\n      fprintf(stderr, \"%s\\n\", StringValuePtr(ruby_build_info));\n\n    fprintf(stderr, \"\\nTry installing debug symbols (apt-get install libruby1.8-dbg or similar), or using a \"\n                    \"Ruby built with RVM (http://rvm.beginrescueend.com/)\\n\\n\");\n\n    errx(EX_SOFTWARE, \"If that doesn't work, please email this output to bugs@memprof.com\");\n  }\n}\n\nvoid\nInit_memprof()\n{\n  VALUE memprof = rb_define_module(\"Memprof\");\n  eUnsupported = rb_define_class_under(memprof, \"Unsupported\", rb_eStandardError);\n  rb_define_singleton_method(memprof, \"start\", memprof_start, 0);\n  rb_define_singleton_method(memprof, \"stop\", memprof_stop, 0);\n  rb_define_singleton_method(memprof, \"stats\", memprof_stats, -1);\n  rb_define_singleton_method(memprof, \"stats!\", memprof_stats_bang, -1);\n  rb_define_singleton_method(memprof, \"track\", memprof_track, -1);\n  rb_define_singleton_method(memprof, \"dump\", memprof_dump, -1);\n  rb_define_singleton_method(memprof, \"dump_all\", memprof_dump_all, -1);\n  rb_define_singleton_method(memprof, \"trace\", memprof_trace, -1);\n  rb_define_singleton_method(memprof, \"trace_request\", memprof_trace_request, 1);\n  rb_define_singleton_method(memprof, \"trace_filename\", memprof_trace_filename_get, 0);\n  rb_define_singleton_method(memprof, \"trace_filename=\", memprof_trace_filename_set, -1);\n\n  objs = st_init_numtable();\n  init_memprof_config_base();\n  bin_init();\n  init_memprof_config_extended();\n  create_tramp_table();\n\n  install_malloc_tracer();\n  install_gc_tracer();\n  install_objects_tracer();\n  install_fd_tracer();\n  install_mysql_tracer();\n  install_postgres_tracer();\n  install_memcache_tracer();\n  install_resources_tracer();\n\n  gc_hook = Data_Wrap_Struct(rb_cObject, sourcefile_marker, NULL, NULL);\n  rb_global_variable(&gc_hook);\n\n  rb_classname = memprof_config.classname;\n  rb_add_freelist = memprof_config.add_freelist;\n  rb_bm_mark = memprof_config.bm_mark;\n  rb_blk_free = memprof_config.blk_free;\n  rb_thread_mark = memprof_config.thread_mark;\n  ptr_to_rb_mark_table_add_filename = memprof_config.rb_mark_table_add_filename;\n\n  assert(rb_classname);\n\n  return;\n}\n"
  },
  {
    "path": "ext/mmap.h",
    "content": "/*\n * Following code taken from http://thebends.googlecode.com/svn/trunk/mach-o/mmap.c\n * Copyright: Allen Porter <allen@thebends.org>\n */\n#ifndef __MMAP_H__\n#define __MMAP_H__\n\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <mach/vm_param.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/mman.h>\n#include <unistd.h>\n\n/*\n * Simple utililty methods for mmaping files.\n */\n\nstruct mmap_info {\n  const char *name; /* filename */\n  int fd;           /* file descriptor */\n  size_t data_size; /* size of file */\n  void* data;       /* mmap'd writable contents of file */\n};\n\n/*\n * Open a file, memory map its contents, and populate mmap_info. Requires a\n * filename.\n */\nint mmap_file_open(struct mmap_info *file_info) {\n  assert(file_info != NULL);\n  file_info->fd = open(file_info->name, O_RDONLY);\n  if (file_info->fd < 0) {\n    perror(\"open\");\n    return -1;\n  }\n  struct stat sb;\n  if (fstat(file_info->fd, &sb) < 0) {\n    perror(\"fstat\");\n    return -1;\n  }\n  file_info->data_size = (size_t)sb.st_size;\n  size_t map_size = file_info->data_size;\n  file_info->data = mmap(0, map_size,\n                         PROT_READ,\n                         MAP_FILE | MAP_SHARED,\n                         file_info->fd, /* offset */0);\n  if (file_info->data == MAP_FAILED) {\n    perror(\"mmap\");\n    return -1;\n  }\n  return 0;\n}\n\n/*\n * Unmemory map and close the file in file_info.\n */\nint munmap_file(struct mmap_info *file_info) {\n  if (munmap(file_info->data, file_info->data_size) < 0) {\n    perror(\"munmap\");\n    return -1;\n  }\n  if (close(file_info->fd) < 0) {\n    perror(\"close\");\n    return -1;\n  }\n  return 0;\n}\n\n#endif  /* __MMAP_H__ */"
  },
  {
    "path": "ext/tracer.c",
    "content": "#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"util.h\"\n\nstatic json_gen tracing_json_gen = NULL;\n\n/*\n   XXX if we ever need a linked list for anything else ever, remove this crap\n       and switch to a generic macro-based linked list implementation\n*/\nstruct tracer_list {\n  struct tracer *tracer;\n  struct tracer_list *next;\n};\n\nstatic struct tracer_list *tracer_list = NULL;\n\nint\ntrace_insert(struct tracer *trace)\n{\n  assert(trace != NULL);\n\n  struct tracer_list *entry = malloc(sizeof(*entry));\n  entry->tracer = trace;\n\n  entry->next = tracer_list;\n  tracer_list = entry;\n  return 0;\n}\n\nint\ntrace_remove(const char *id)\n{\n  struct tracer_list *tmp, *prev;\n  tmp = prev = tracer_list;\n\n  while (tmp) {\n    if (strcmp(id, tmp->tracer->id) == 0) {\n      tmp->next = tmp->next;\n      free(tmp->tracer);\n      free(tmp);\n      return 0;\n    }\n    prev = tmp;\n    tmp = tmp->next;\n  }\n\n  return 1;\n}\n\nstatic void\ndo_trace_invoke(struct tracer *trace, trace_fn fn)\n{\n  switch (fn) {\n    case TRACE_START:\n      trace->start();\n      break;\n    case TRACE_STOP:\n      trace->stop();\n      break;\n    case TRACE_RESET:\n      trace->reset();\n      break;\n    case TRACE_DUMP:\n      json_gen_cstr(tracing_json_gen, trace->id);\n      json_gen_map_open(tracing_json_gen);\n      trace->dump(tracing_json_gen);\n      json_gen_map_close(tracing_json_gen);\n      break;\n    default:\n      dbg_printf(\"invoked a non-existant trace function type: %d\", fn);\n      assert(1==0);\n  }\n\n  return;\n}\n\nint\ntrace_invoke_all(trace_fn fn)\n{\n  struct tracer_list *tmp = tracer_list;\n  while (tmp) {\n    do_trace_invoke(tmp->tracer, fn);\n    tmp = tmp->next;\n  }\n  return 0;\n}\n\nint\ntrace_invoke(const char *id, trace_fn fn)\n{\n  struct tracer_list *tmp = tracer_list;\n  while (tmp) {\n    if (strcmp(id, tmp->tracer->id) == 0) {\n      do_trace_invoke(tmp->tracer, fn);\n    }\n    tmp = tmp->next;\n  }\n  return 0;\n}\n\nvoid\ntrace_set_output(json_gen gen)\n{\n  tracing_json_gen = gen;\n}\n\njson_gen\ntrace_get_output()\n{\n  return tracing_json_gen;\n}\n"
  },
  {
    "path": "ext/tracer.h",
    "content": "#if !defined(__TRACER__H_)\n#define __TRACER__H_\n\n#include \"json.h\"\n\nstruct tracer {\n  char *id;\n  void (*start)();\n  void (*stop)();\n  void (*reset)();\n  void (*dump)(json_gen);\n};\n\ntypedef enum {\n  TRACE_START,\n  TRACE_STOP,\n  TRACE_RESET,\n  TRACE_DUMP,\n} trace_fn;\n\nint\ntrace_insert(struct tracer *trace);\n\nint\ntrace_remove(const char *id);\n\nint\ntrace_invoke_all(trace_fn fn);\n\nint\ntrace_invoke(const char *id, trace_fn fn);\n\nvoid\ntrace_set_output(json_gen gen);\n\njson_gen\ntrace_get_output();\n\n/* for now, these will live here */\nextern void install_malloc_tracer();\nextern void install_gc_tracer();\nextern void install_fd_tracer();\nextern void install_mysql_tracer();\nextern void install_postgres_tracer();\nextern void install_objects_tracer();\nextern void install_memcache_tracer();\nextern void install_resources_tracer();\n#endif\n"
  },
  {
    "path": "ext/tracers/fd.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <poll.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/select.h>\n#include <sys/socket.h>\n#include <sys/time.h>\n#include <unistd.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_fd_stats {\n  size_t read_calls;\n  uint32_t read_time;\n  size_t read_requested_bytes;\n  size_t read_actual_bytes;\n\n  size_t write_calls;\n  uint32_t write_time;\n  size_t write_requested_bytes;\n  size_t write_actual_bytes;\n\n  size_t recv_calls;\n  uint32_t recv_time;\n  ssize_t recv_actual_bytes;\n\n  size_t connect_calls;\n  uint32_t connect_time;\n\n  size_t select_calls;\n  uint32_t select_time;\n\n  size_t poll_calls;\n  uint32_t poll_time;\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_fd_stats stats;\n\nstatic ssize_t\nread_tramp(int fildes, void *buf, size_t nbyte) {\n  uint64_t millis = 0;\n  int err;\n  ssize_t ret;\n\n  millis = timeofday_ms();\n  ret = read(fildes, buf, nbyte);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.read_time += millis;\n  stats.read_calls++;\n  stats.read_requested_bytes += nbyte;\n  if (ret > 0)\n    stats.read_actual_bytes += ret;\n\n  errno = err;\n  return ret;\n}\n\nstatic ssize_t\nwrite_tramp(int fildes, const void *buf, size_t nbyte) {\n  uint64_t millis = 0;\n  int err;\n  ssize_t ret;\n\n  millis = timeofday_ms();\n  ret = write(fildes, buf, nbyte);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.write_time += millis;\n  stats.write_calls++;\n  stats.write_requested_bytes += nbyte;\n  if (ret > 0)\n    stats.write_actual_bytes += ret;\n\n  errno = err;\n  return ret;\n}\n\nstatic ssize_t\nrecv_tramp(int socket, void *buffer, size_t length, int flags) {\n  uint64_t millis = 0;\n  int err;\n  ssize_t ret;\n\n  millis = timeofday_ms();\n  ret = recv(socket, buffer, length, flags);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.recv_time += millis;\n  stats.recv_calls++;\n  if (ret > 0)\n    stats.recv_actual_bytes += ret;\n\n  errno = err;\n  return ret;\n}\n\nstatic int\nconnect_tramp(int socket, const struct sockaddr *address, socklen_t address_len) {\n  uint64_t millis = 0;\n  int err, ret;\n\n  millis = timeofday_ms();\n  ret = connect(socket, address, address_len);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.connect_time += millis;\n  stats.connect_calls++;\n\n  errno = err;\n  return ret;\n}\n\nstatic int\nselect_tramp(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)\n{\n  uint64_t millis = 0;\n  int ret, err;\n\n  millis = timeofday_ms();\n  ret = select(nfds, readfds, writefds, errorfds, timeout);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.select_time += millis;\n  stats.select_calls++;\n\n  errno = err;\n  return ret;\n}\n\nstatic int\npoll_tramp(struct pollfd fds[], nfds_t nfds, int timeout)\n{\n  uint64_t millis = 0;\n  int ret, err;\n\n  millis = timeofday_ms();\n  ret = poll(fds, nfds, timeout);\n  err = errno;\n  millis = timeofday_ms() - millis;\n\n  stats.poll_time += millis;\n  stats.poll_calls++;\n\n  errno = err;\n  return ret;\n}\n\nstatic void\nfd_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  insert_tramp(\"read\", read_tramp);\n  insert_tramp(\"write\", write_tramp);\n  insert_tramp(\"poll\", poll_tramp);\n\n  #ifdef HAVE_MACH\n  insert_tramp(\"select$DARWIN_EXTSN\", select_tramp);\n  #else\n  insert_tramp(\"select\", select_tramp);\n  insert_tramp(\"connect\", connect_tramp);\n  insert_tramp(\"recv\", recv_tramp);\n  #endif\n}\n\nstatic void\nfd_trace_stop() {\n}\n\nstatic void\nfd_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\nfd_trace_dump(json_gen gen) {\n  if (stats.read_calls > 0) {\n    json_gen_cstr(gen, \"read\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.read_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.read_time);\n    json_gen_cstr(gen, \"requested\");\n    json_gen_integer(gen, stats.read_requested_bytes);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.read_actual_bytes);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.write_calls > 0) {\n    json_gen_cstr(gen, \"write\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.write_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.write_time);\n    json_gen_cstr(gen, \"requested\");\n    json_gen_integer(gen, stats.write_requested_bytes);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.write_actual_bytes);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.recv_calls > 0) {\n    json_gen_cstr(gen, \"recv\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.recv_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.recv_time);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.recv_actual_bytes);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.connect_calls > 0) {\n    json_gen_cstr(gen, \"connect\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.connect_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.connect_time);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.select_calls > 0) {\n    json_gen_cstr(gen, \"select\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.select_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.select_time);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.poll_calls > 0) {\n    json_gen_cstr(gen, \"poll\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.poll_calls);\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.poll_time);\n    json_gen_map_close(gen);\n  }\n}\n\nvoid install_fd_tracer()\n{\n  tracer.start = fd_trace_start;\n  tracer.stop = fd_trace_stop;\n  tracer.reset = fd_trace_reset;\n  tracer.dump = fd_trace_dump;\n  tracer.id = \"fd\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/gc.c",
    "content": "#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n#include <sys/resource.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_gc_stats {\n  size_t gc_calls;\n  uint32_t gc_time;\n  uint32_t gc_utime;\n  uint32_t gc_stime;\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_gc_stats stats;\nstatic void (*orig_garbage_collect)();\n\nstatic void\ngc_tramp()\n{\n  uint64_t millis = 0;\n  struct rusage usage_start, usage_end;\n\n  millis = timeofday_ms();\n  getrusage(RUSAGE_SELF, &usage_start);\n  orig_garbage_collect();\n  getrusage(RUSAGE_SELF, &usage_end);\n  millis = timeofday_ms() - millis;\n\n  stats.gc_time += millis;\n  stats.gc_calls++;\n\n  stats.gc_utime += TVAL_TO_INT64(usage_end.ru_utime) - TVAL_TO_INT64(usage_start.ru_utime);\n  stats.gc_stime += TVAL_TO_INT64(usage_end.ru_stime) - TVAL_TO_INT64(usage_start.ru_stime);\n}\n\nstatic void\ngc_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  orig_garbage_collect = bin_find_symbol(\"garbage_collect\", NULL, 0);\n  assert(orig_garbage_collect != NULL);\n  dbg_printf(\"orig_garbage_collect: %p\\n\", orig_garbage_collect);\n\n  insert_tramp(\"garbage_collect\", gc_tramp);\n}\n\nstatic void\ngc_trace_stop() {\n}\n\nstatic void\ngc_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\ngc_trace_dump(json_gen gen) {\n  json_gen_cstr(gen, \"calls\");\n  json_gen_integer(gen, stats.gc_calls);\n\n  json_gen_cstr(gen, \"time\");\n  json_gen_integer(gen, stats.gc_time);\n\n  json_gen_cstr(gen, \"utime\");\n  json_gen_integer(gen, stats.gc_utime);\n\n  json_gen_cstr(gen, \"stime\");\n  json_gen_integer(gen, stats.gc_stime);\n}\n\nvoid install_gc_tracer()\n{\n  tracer.start = gc_trace_start;\n  tracer.stop = gc_trace_stop;\n  tracer.reset = gc_trace_reset;\n  tracer.dump = gc_trace_dump;\n  tracer.id = \"gc\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/memcache.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_memcache_stats {\n  size_t get_calls;\n  size_t get_responses[45];\n\n  size_t set_calls;\n  size_t set_responses[45];\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_memcache_stats stats;\nstatic const char* (*_memcached_lib_version)(void);\nstatic char* (*_memcached_get)(void *ptr, const char *key, size_t key_length, size_t *value_length, uint32_t *flags, void *error);\nstatic int (*_memcached_set)(void *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags);\n\nstatic char*\nmemcached_get_tramp(void *ptr, const char *key, size_t key_length, size_t *value_length, uint32_t *flags, void *error)\n{\n  char* ret = _memcached_get(ptr, key, key_length, value_length, flags, error);\n  stats.get_calls++;\n  int err = *(int*)error;\n  stats.get_responses[err > 42 ? 44 : err]++;\n  return ret;\n}\n\nstatic int\nmemcached_set_tramp(void *ptr, const char *key, size_t key_length, const char *value, size_t value_length, time_t expiration, uint32_t flags)\n{\n  int ret = _memcached_set(ptr, key, key_length, value, value_length, expiration, flags);\n  stats.set_calls++;\n  stats.set_responses[ret > 42 ? 44 : ret]++;\n  return ret;\n}\n\nstatic void\nmemcache_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  _memcached_lib_version = bin_find_symbol(\"memcached_lib_version\", NULL, 1);\n  if (_memcached_lib_version) {\n    const char *version = _memcached_lib_version();\n    if (strcmp(version, \"0.32\") == 0) {\n      _memcached_get = bin_find_symbol(\"memcached_get\", NULL, 1);\n      insert_tramp(\"memcached_get\", memcached_get_tramp);\n\n      _memcached_set = bin_find_symbol(\"memcached_set\", NULL, 1);\n      insert_tramp(\"memcached_set\", memcached_set_tramp);\n    }\n  }\n}\n\nstatic void\nmemcache_trace_stop() {\n}\n\nstatic void\nmemcache_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\nmemcache_trace_dump_results(json_gen gen, size_t responses[])\n{\n  int i;\n  json_gen_cstr(gen, \"responses\");\n  json_gen_map_open(gen);\n  for (i=0; i < 45; i++) {\n    if (responses[i]) {\n      switch (i) {\n        case 0:\n          json_gen_cstr(gen, \"success\");\n          break;\n        case 16:\n          json_gen_cstr(gen, \"notfound\");\n          break;\n        case 44:\n          json_gen_cstr(gen, \"unknown\");\n          break;\n        default:\n          json_gen_format(gen, \"%d\", i);\n      }\n      json_gen_integer(gen, responses[i]);\n    }\n  }\n  json_gen_map_close(gen);\n}\n\nstatic void\nmemcache_trace_dump(json_gen gen) {\n  if (stats.get_calls > 0) {\n    json_gen_cstr(gen, \"get\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.get_calls);\n    memcache_trace_dump_results(gen, stats.get_responses);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.set_calls > 0) {\n    json_gen_cstr(gen, \"set\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.set_calls);\n    memcache_trace_dump_results(gen, stats.set_responses);\n    json_gen_map_close(gen);\n  }\n}\n\nvoid install_memcache_tracer()\n{\n  tracer.start = memcache_trace_start;\n  tracer.stop = memcache_trace_stop;\n  tracer.reset = memcache_trace_reset;\n  tracer.dump = memcache_trace_dump;\n  tracer.id = \"memcache\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/memory.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_memory_stats {\n  size_t malloc_bytes_requested;\n  size_t calloc_bytes_requested;\n  size_t realloc_bytes_requested;\n\n  size_t malloc_bytes_actual;\n  size_t calloc_bytes_actual;\n  size_t realloc_bytes_actual;\n  size_t free_bytes_actual;\n\n  size_t malloc_calls;\n  size_t calloc_calls;\n  size_t realloc_calls;\n  size_t free_calls;\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_memory_stats stats;\nstatic size_t (*malloc_usable_size)(void *ptr);\n\nstatic void *\nmalloc_tramp(size_t size)\n{\n  void *ret = NULL;\n  int err;\n\n  ret = malloc(size);\n  err = errno;\n\n  stats.malloc_bytes_requested += size;\n  stats.malloc_calls++;\n\n  if (ret)\n    stats.malloc_bytes_actual += malloc_usable_size(ret);\n\n  errno = err;\n  return ret;\n}\n\nstatic void *\ncalloc_tramp(size_t nmemb, size_t size)\n{\n  void *ret = NULL;\n  int err;\n\n  ret = calloc(nmemb, size);\n  err = errno;\n\n  stats.calloc_bytes_requested += (nmemb * size);\n  stats.calloc_calls++;\n\n  if (ret)\n    stats.calloc_bytes_actual += malloc_usable_size(ret);\n\n  errno = err;\n  return ret;\n}\n\nstatic void *\nrealloc_tramp(void *ptr, size_t size)\n{\n  void *ret = NULL;\n  int err;\n\n  ret = realloc(ptr, size);\n  err = errno;\n\n  stats.realloc_bytes_requested += size;\n  stats.realloc_calls++;\n\n  if (ret)\n    stats.realloc_bytes_actual += malloc_usable_size(ret);\n\n  errno = err;\n  return ret;\n}\n\nstatic void\nfree_tramp(void *ptr)\n{\n  if (ptr)\n    stats.free_bytes_actual += malloc_usable_size(ptr);\n\n  stats.free_calls++;\n\n  free(ptr);\n}\n\nstatic void\nmalloc_trace_start()\n{\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  if (!malloc_usable_size) {\n    malloc_usable_size = bin_find_symbol(\"MallocExtension_GetAllocatedSize\", NULL, 1);\n    if (!malloc_usable_size) {\n      dbg_printf(\"tcmalloc was not found...\\n\");\n      malloc_usable_size = bin_find_symbol(\"malloc_usable_size\", NULL, 1);\n    }\n    if (!malloc_usable_size) {\n      dbg_printf(\"malloc_usable_size was not found...\\n\");\n      malloc_usable_size = bin_find_symbol(\"malloc_size\", NULL, 1);\n    }\n    assert(malloc_usable_size != NULL);\n    dbg_printf(\"malloc_usable_size: %p\\n\", malloc_usable_size);\n  }\n\n  insert_tramp(\"malloc\", malloc_tramp);\n  insert_tramp(\"realloc\", realloc_tramp);\n  insert_tramp(\"calloc\", calloc_tramp);\n  insert_tramp(\"free\", free_tramp);\n}\n\nstatic void\nmalloc_trace_stop()\n{\n}\n\nstatic void\nmalloc_trace_reset()\n{\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\nmalloc_trace_dump(json_gen gen)\n{\n  if (stats.malloc_calls > 0) {\n    json_gen_cstr(gen, \"malloc\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.malloc_calls);\n    json_gen_cstr(gen, \"requested\");\n    json_gen_integer(gen, stats.malloc_bytes_requested);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.malloc_bytes_actual);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.realloc_calls > 0) {\n    json_gen_cstr(gen, \"realloc\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.realloc_calls);\n    json_gen_cstr(gen, \"requested\");\n    json_gen_integer(gen, stats.realloc_bytes_requested);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.realloc_bytes_actual);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.calloc_calls > 0) {\n    json_gen_cstr(gen, \"calloc\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.calloc_calls);\n    json_gen_cstr(gen, \"requested\");\n    json_gen_integer(gen, stats.calloc_bytes_requested);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.calloc_bytes_actual);\n    json_gen_map_close(gen);\n  }\n\n  if (stats.free_calls > 0) {\n    json_gen_cstr(gen, \"free\");\n    json_gen_map_open(gen);\n    json_gen_cstr(gen, \"calls\");\n    json_gen_integer(gen, stats.free_calls);\n    json_gen_cstr(gen, \"actual\");\n    json_gen_integer(gen, stats.free_bytes_actual);\n    json_gen_map_close(gen);\n  }\n}\n\nvoid install_malloc_tracer()\n{\n  tracer.start = malloc_trace_start;\n  tracer.stop = malloc_trace_stop;\n  tracer.reset = malloc_trace_reset;\n  tracer.dump = malloc_trace_dump;\n  tracer.id = \"memory\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/mysql.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tracers/sql.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_mysql_stats {\n  size_t query_calls;\n  uint32_t query_time;\n\n  size_t query_calls_by_type[sql_UNKNOWN+1];\n  uint32_t query_time_by_type[sql_UNKNOWN+1];\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_mysql_stats stats;\n\nstatic int (*orig_real_query)(void *mysql, const char *stmt_str, unsigned long length);\nstatic int (*orig_send_query)(void *mysql, const char *stmt_str, unsigned long length);\n\nstatic int\nreal_query_tramp(void *mysql, const char *stmt_str, unsigned long length) {\n  enum memprof_sql_type type;\n  uint64_t millis = 0;\n  int ret;\n\n  millis = timeofday_ms();\n  ret = orig_real_query(mysql, stmt_str, length);\n  millis = timeofday_ms() - millis;\n\n  stats.query_time += millis;\n  stats.query_calls++;\n\n  type = memprof_sql_query_type(stmt_str, length);\n  stats.query_time_by_type[type] += millis;\n  stats.query_calls_by_type[type]++;\n\n  return ret;\n}\n\nstatic int\nsend_query_tramp(void *mysql, const char *stmt_str, unsigned long length) {\n  enum memprof_sql_type type;\n  int ret;\n\n  ret = orig_send_query(mysql, stmt_str, length);\n  stats.query_calls++;\n\n  type = memprof_sql_query_type(stmt_str, length);\n  stats.query_calls_by_type[type]++;\n\n  return ret;\n}\n\nstatic void\nmysql_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  orig_real_query = bin_find_symbol(\"mysql_real_query\", NULL, 1);\n  if (orig_real_query)\n    insert_tramp(\"mysql_real_query\", real_query_tramp);\n\n  orig_send_query = bin_find_symbol(\"mysql_send_query\", NULL, 1);\n  if (orig_send_query)\n    insert_tramp(\"mysql_send_query\", send_query_tramp);\n}\n\nstatic void\nmysql_trace_stop() {\n}\n\nstatic void\nmysql_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\nmysql_trace_dump(json_gen gen) {\n  enum memprof_sql_type i;\n\n  if (stats.query_calls > 0) {\n    json_gen_cstr(gen, \"queries\");\n    json_gen_integer(gen, stats.query_calls);\n\n    json_gen_cstr(gen, \"time\");\n    json_gen_integer(gen, stats.query_time);\n\n    json_gen_cstr(gen, \"types\");\n    json_gen_map_open(gen);\n    for (i=0; i<=sql_UNKNOWN; i++) {\n      json_gen_cstr(gen, memprof_sql_type_str(i));\n      json_gen_map_open(gen);\n\n      json_gen_cstr(gen, \"queries\");\n      json_gen_integer(gen, stats.query_calls_by_type[i]);\n\n      json_gen_cstr(gen, \"time\");\n      json_gen_integer(gen, stats.query_time_by_type[i]);\n\n      json_gen_map_close(gen);\n    }\n    json_gen_map_close(gen);\n  }\n}\n\nvoid install_mysql_tracer()\n{\n  tracer.start = mysql_trace_start;\n  tracer.stop = mysql_trace_stop;\n  tracer.reset = mysql_trace_reset;\n  tracer.dump = mysql_trace_dump;\n  tracer.id = \"mysql\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/objects.c",
    "content": "#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n#include \"ruby.h\"\n\nstruct memprof_objects_stats {\n  size_t newobj_calls;\n  size_t types[T_MASK+1];\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_objects_stats stats;\nstatic VALUE (*orig_rb_newobj)();\n\nstatic VALUE last_obj = 0;\nstatic VALUE gc_hook = 0;\n\nstatic void\nrecord_last_obj()\n{\n  if (last_obj) {\n    stats.types[BUILTIN_TYPE(last_obj)]++;\n    last_obj = 0;\n  }\n}\n\nstatic VALUE\nobjects_tramp() {\n  record_last_obj();\n  stats.newobj_calls++;\n  last_obj = orig_rb_newobj();\n  return last_obj;\n}\n\nstatic void\nobjects_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  orig_rb_newobj = bin_find_symbol(\"rb_newobj\", NULL, 0);\n  assert(orig_rb_newobj != NULL);\n  dbg_printf(\"orig_rb_newobj: %p\\n\", orig_rb_newobj);\n\n  insert_tramp(\"rb_newobj\", objects_tramp);\n}\n\nstatic void\nobjects_trace_stop() {\n}\n\nstatic void\nobjects_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n  last_obj = 0;\n}\n\nstatic inline char*\ntype_string(int type) {\n  switch (type) {\n    case T_NONE:\n      return \"none\";\n    case T_NIL:\n      return \"nil\";\n    case T_OBJECT:\n      return \"object\";\n    case T_CLASS:\n      return \"class\";\n    case T_ICLASS:\n      return \"iclass\";\n    case T_MODULE:\n      return \"module\";\n    case T_FLOAT:\n      return \"float\";\n    case T_STRING:\n      return \"string\";\n    case T_REGEXP:\n      return \"regexp\";\n    case T_ARRAY:\n      return \"array\";\n    case T_FIXNUM:\n      return \"fixnum\";\n    case T_HASH:\n      return \"hash\";\n    case T_STRUCT:\n      return \"struct\";\n    case T_BIGNUM:\n      return \"bignum\";\n    case T_FILE:\n      return \"file\";\n    case T_TRUE:\n      return \"true\";\n    case T_FALSE:\n      return \"false\";\n    case T_DATA:\n      return \"data\";\n    case T_MATCH:\n      return \"match\";\n    case T_SYMBOL:\n      return \"symbol\";\n    case T_BLKTAG:\n      return \"blktag\";\n    case T_UNDEF:\n      return \"undef\";\n    case T_VARMAP:\n      return \"varmap\";\n    case T_SCOPE:\n      return \"scope\";\n    case T_NODE:\n      return \"node\";\n    default:\n      return \"unknown\";\n  }\n}\n\nstatic void\nobjects_trace_dump(json_gen gen) {\n  int i;\n  record_last_obj();\n\n  json_gen_cstr(gen, \"created\");\n  json_gen_integer(gen, stats.newobj_calls);\n\n  json_gen_cstr(gen, \"types\");\n  json_gen_map_open(gen);\n  for (i=0; i<T_MASK+1; i++) {\n    if (stats.types[i] > 0) {\n      json_gen_cstr(gen, type_string(i));\n      json_gen_integer(gen, stats.types[i]);\n    }\n  }\n  json_gen_map_close(gen);\n}\n\nvoid install_objects_tracer()\n{\n  if (!gc_hook) {\n    gc_hook = Data_Wrap_Struct(rb_cObject, record_last_obj, NULL, NULL);\n    rb_global_variable(&gc_hook);\n  }\n\n  tracer.start = objects_trace_start;\n  tracer.stop = objects_trace_stop;\n  tracer.reset = objects_trace_reset;\n  tracer.dump = objects_trace_dump;\n  tracer.id = \"objects\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/postgres.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tracers/sql.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_postgres_stats {\n  size_t query_calls;\n  size_t query_calls_by_type[sql_UNKNOWN+1];\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_postgres_stats stats;\nstatic void * (*orig_PQexec)(void *postgres, const char *stmt);\n\nstatic void *\nPQexec_tramp(void *postgres, const char *stmt) {\n  enum memprof_sql_type type;\n  void *ret;\n\n  ret = orig_PQexec(postgres, stmt);\n  stats.query_calls++;\n\n  type = memprof_sql_query_type(stmt, strlen(stmt));\n  stats.query_calls_by_type[type]++;\n\n  return ret;\n}\n\nstatic void\npostgres_trace_start() {\n  static int inserted = 0;\n\n  if (!inserted)\n    inserted = 1;\n  else\n    return;\n\n  orig_PQexec = bin_find_symbol(\"PQexec\", NULL, 1);\n  if (orig_PQexec)\n    insert_tramp(\"PQexec\", PQexec_tramp);\n}\n\nstatic void\npostgres_trace_stop() {\n}\n\nstatic void\npostgres_trace_reset() {\n  memset(&stats, 0, sizeof(stats));\n}\n\nstatic void\npostgres_trace_dump(json_gen gen) {\n  enum memprof_sql_type i;\n\n  if (stats.query_calls > 0) {\n    json_gen_cstr(gen, \"queries\");\n    json_gen_integer(gen, stats.query_calls);\n\n    json_gen_cstr(gen, \"types\");\n    json_gen_map_open(gen);\n    for (i=0; i<=sql_UNKNOWN; i++) {\n      json_gen_cstr(gen, memprof_sql_type_str(i));\n      json_gen_map_open(gen);\n      json_gen_cstr(gen, \"queries\");\n      json_gen_integer(gen, stats.query_calls_by_type[i]);\n      json_gen_map_close(gen);\n    }\n    json_gen_map_close(gen);\n  }\n}\n\nvoid install_postgres_tracer()\n{\n  tracer.start = postgres_trace_start;\n  tracer.stop = postgres_trace_stop;\n  tracer.reset = postgres_trace_reset;\n  tracer.dump = postgres_trace_dump;\n  tracer.id = \"postgres\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/resources.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n#include <sys/resource.h>\n\n#include \"json.h\"\n#include \"tracer.h\"\n#include \"tramp.h\"\n#include \"util.h\"\n\nstruct memprof_resources_stats {\n  long nsignals;\n\n  long inblock;\n  long oublock;\n\n  int64_t utime;\n  int64_t stime;\n};\n\nstatic struct tracer tracer;\nstatic struct memprof_resources_stats stats;\n\nstatic void\nresources_trace_start() {\n  struct rusage usage;\n  getrusage(RUSAGE_SELF, &usage);\n\n  stats.nsignals = -usage.ru_nsignals;\n\n  stats.inblock = -usage.ru_inblock;\n  stats.oublock = -usage.ru_oublock;\n\n  stats.stime = -TVAL_TO_INT64(usage.ru_stime);\n  stats.utime = -TVAL_TO_INT64(usage.ru_utime);\n}\n\nstatic void\nresources_trace_dump(json_gen gen) {\n  { // calculate diff before dump, since stop is called after dump\n    struct rusage usage;\n    getrusage(RUSAGE_SELF, &usage);\n\n    stats.nsignals += usage.ru_nsignals;\n\n    stats.inblock += usage.ru_inblock;\n    stats.oublock += usage.ru_oublock;\n\n    stats.stime += TVAL_TO_INT64(usage.ru_stime);\n    stats.utime += TVAL_TO_INT64(usage.ru_utime);\n  }\n\n  json_gen_cstr(gen, \"signals\");\n  json_gen_integer(gen, stats.nsignals);\n\n  json_gen_cstr(gen, \"inputs\");\n  json_gen_integer(gen, stats.inblock);\n\n  json_gen_cstr(gen, \"outputs\");\n  json_gen_integer(gen, stats.oublock);\n\n  json_gen_cstr(gen, \"stime\");\n  json_gen_integer(gen, stats.stime);\n\n  json_gen_cstr(gen, \"utime\");\n  json_gen_integer(gen, stats.utime);\n}\n\nstatic void\nresources_trace_stop() {\n}\n\nstatic void\nresources_trace_reset() {\n}\n\nvoid install_resources_tracer()\n{\n  tracer.start = resources_trace_start;\n  tracer.stop = resources_trace_stop;\n  tracer.reset = resources_trace_reset;\n  tracer.dump = resources_trace_dump;\n  tracer.id = \"resources\";\n\n  trace_insert(&tracer);\n}\n"
  },
  {
    "path": "ext/tracers/sql.c",
    "content": "#include <tracers/sql.h>\n\nenum memprof_sql_type\nmemprof_sql_query_type(const char *stmt, unsigned long length)\n{\n  int i;\n\n  for (i=0; i<length && i<10; i++) {\n    switch (stmt[i]) {\n      case ' ':\n      case '\\n':\n      case '\\r':\n        continue;\n        break;\n\n      case 'S':\n      case 's':\n        return sql_SELECT;\n\n      case 'I':\n      case 'i':\n        return sql_INSERT;\n\n      case 'U':\n      case 'u':\n        return sql_UPDATE;\n\n      case 'D':\n      case 'd':\n        return sql_DELETE;\n\n      default:\n        return sql_UNKNOWN;\n    }\n  }\n\n  return sql_UNKNOWN;\n}\n\nconst char *\nmemprof_sql_type_str(enum memprof_sql_type type)\n{\n  switch (type) {\n    case sql_SELECT:\n      return \"select\";\n    case sql_UPDATE:\n      return \"update\";\n    case sql_INSERT:\n      return \"insert\";\n    case sql_DELETE:\n      return \"delete\";\n    default:\n    case sql_UNKNOWN:\n      return \"unknown\";\n  }\n}\n"
  },
  {
    "path": "ext/tracers/sql.h",
    "content": "#if !defined(_sql_h_)\n#define _sql_h_\n\nenum memprof_sql_type {\n  sql_SELECT,\n  sql_UPDATE,\n  sql_INSERT,\n  sql_DELETE,\n  sql_UNKNOWN // last\n};\n\nenum memprof_sql_type\nmemprof_sql_query_type(const char *stmt, unsigned long length);\n\nconst char *\nmemprof_sql_type_str(enum memprof_sql_type);\n\n#endif\n"
  },
  {
    "path": "ext/tramp.c",
    "content": "#include <ruby.h>\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <sysexits.h>\n#include <sys/mman.h>\n#include <err.h>\n#include <assert.h>\n\n#include \"arch.h\"\n#include \"bin_api.h\"\n#include \"util.h\"\n\n#define FREELIST_INLINES 3\n\n/*\n * trampoline specific stuff\n */\nstatic struct tramp_st2_entry *tramp_table;\nstatic size_t tramp_size;\n\n/*\n * inline trampoline specific stuff\n */\nstatic size_t inline_tramp_size;\nstatic struct inline_tramp_st2_entry *inline_tramp_table;\n\nextern struct memprof_config memprof_config;\n\nvoid\ncreate_tramp_table()\n{\n  size_t i;\n  void *region, *ent, *inline_ent;\n  size_t tramp_sz = 0, inline_tramp_sz = 0;\n\n  ent = arch_get_st2_tramp(&tramp_sz);\n  inline_ent = arch_get_inline_st2_tramp(&inline_tramp_sz);\n  assert(ent && inline_ent);\n\n  region = bin_allocate_page();\n  if (region == MAP_FAILED)\n    errx(EX_SOFTWARE, \"Failed to allocate memory for stage 1 trampolines.\");\n\n  tramp_table = region;\n  inline_tramp_table = region + memprof_config.pagesize / 2;\n\n  for (i = 0; i < (memprof_config.pagesize / 2) / tramp_sz; i++) {\n    memcpy(tramp_table + i, ent, tramp_sz);\n  }\n\n  for (i = 0; i < (memprof_config.pagesize / 2) / inline_tramp_sz; i++) {\n    memcpy(inline_tramp_table + i, inline_ent, inline_tramp_sz);\n  }\n}\n\nstatic void\nhook_freelist(int entry, void *tramp)\n{\n  size_t sizes[FREELIST_INLINES];\n  void *freelist_inliners[FREELIST_INLINES];\n  void *freelist = NULL;\n  unsigned char *byte = NULL;\n  int i = 0, tramps_completed = 0;\n\n  assert(memprof_config.gc_sweep != NULL);\n  assert(memprof_config.finalize_list != NULL);\n  assert(memprof_config.rb_gc_force_recycle != NULL);\n  assert(memprof_config.freelist != NULL);\n  assert(memprof_config.gc_sweep_size > 0);\n  assert(memprof_config.finalize_list_size > 0);\n  assert(memprof_config.rb_gc_force_recycle_size > 0);\n\n  freelist_inliners[0] = memprof_config.gc_sweep;\n  freelist_inliners[1] = memprof_config.finalize_list;\n  freelist_inliners[2] = memprof_config.rb_gc_force_recycle;\n  sizes[0] = memprof_config.gc_sweep_size;\n  sizes[1] = memprof_config.finalize_list_size;\n  sizes[2] = memprof_config.rb_gc_force_recycle_size;\n\n  freelist = memprof_config.freelist;\n\n  /* start the search for places to insert the inline tramp */\n  byte = freelist_inliners[i];\n\n  while (i < FREELIST_INLINES) {\n    if (arch_insert_inline_st2_tramp(byte, freelist, tramp,\n        &inline_tramp_table[entry]) == 0) {\n      /* insert occurred, so increment internal counters for the tramp table */\n      entry++;\n      inline_tramp_size++;\n\n      /*\n       * add_freelist() only gets inlined *ONCE* into any of the 3 functions\n       * that we're scanning, so move on to the next 'inliner' after we\n       * tramp the first instruction we find.  REE's gc_sweep has 2 calls,\n       * but this gets optimized into a single inlining and a jmp to it.\n       * older patchlevels of 1.8.7 don't have an add_freelist(), but the\n       * instruction should be the same.\n       */\n      tramps_completed++;\n      i++;\n      byte = freelist_inliners[i];\n      continue;\n    }\n\n    /* if we've looked at all the bytes in this function... */\n    if ((size_t)((void *)byte - freelist_inliners[i]) >= sizes[i]) {\n      /* move on to the next function */\n      i++;\n      byte = freelist_inliners[i];\n    }\n    byte++;\n  }\n\n  if (tramps_completed != 3)\n    errx(EX_SOFTWARE, \"Inline add_freelist tramp insertion failed! \"\n         \"Only inserted %d tramps.\", tramps_completed);\n}\n\nvoid\ninsert_tramp(const char *trampee, void *tramp)\n{\n  void *trampee_addr = bin_find_symbol(trampee, NULL, 1);\n  int inline_ent = inline_tramp_size;\n\n  if (trampee_addr == NULL) {\n    if (strcmp(\"add_freelist\", trampee) == 0) {\n      /* XXX super hack */\n      inline_tramp_size++;\n      hook_freelist(inline_ent, tramp /* freelist_tramp() */);\n    } else {\n      errx(EX_SOFTWARE, \"Failed to locate required symbol %s\", trampee);\n    }\n  } else {\n    tramp_table[tramp_size].addr = tramp;\n    if (bin_update_image(trampee, &tramp_table[tramp_size], NULL) != 0)\n      errx(EX_SOFTWARE, \"Failed to insert tramp for %s\", trampee);\n    tramp_size++;\n  }\n}\n"
  },
  {
    "path": "ext/tramp.h",
    "content": "#if !defined(TRAMP__)\n#define TRAMP__\n\n/*\n * create_tramp_table - create the trampoline tables.\n */\nvoid\ncreate_tramp_table();\n\n/*\n * insert_tramp - insert a trampoline.\n *\n * Given:\n *  - trampee: function in which we want to install the trampoline.\n *  - tramp:   pointer to the function to be called from the trampoline.\n *\n * This function is responsible for installing the requested trampoline\n * at the location of \"trampee\".  This results in tramp() being called\n * whenever trampee() is executed.\n */\nvoid\ninsert_tramp(const char *trampee, void *tramp);\n#endif\n"
  },
  {
    "path": "ext/util.c",
    "content": "#include <stdlib.h>\n#include <time.h>\n#include <util.h>\n\n#include <sys/time.h>\n\n/* This is the CRC function used by GNU. Stripped executables may contain a\n * section .gnu_debuglink which holds the name of an elf object with debug\n * information and a checksum.\n *   !!!! DO NOT MODIFY THIS FUNCTION !!!!\n *   TODO create specs for this!\n */\nunsigned long\ngnu_debuglink_crc32(unsigned long crc, unsigned char *buf, size_t len)\n{\n  static const unsigned int crc32_table[256] = {\n    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,\n    0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,\n    0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,\n    0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,\n    0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,\n    0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,\n    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,\n    0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\n    0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,\n    0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,\n    0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,\n    0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,\n    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,\n    0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,\n    0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,\n    0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,\n    0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,\n    0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,\n    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,\n    0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,\n    0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,\n    0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,\n    0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,\n    0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,\n    0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,\n    0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,\n    0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,\n    0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,\n    0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,\n    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,\n    0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,\n    0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,\n    0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,\n    0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,\n    0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,\n    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,\n    0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,\n    0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,\n    0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,\n    0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,\n    0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,\n    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,\n    0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,\n    0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,\n    0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,\n    0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,\n    0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,\n    0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,\n    0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,\n    0x2d02ef8d\n  };\n  unsigned char *end;\n\n  crc = ~crc & 0xffffffff;\n  for (end = buf + len; buf < end; ++buf)\n    crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8);\n  return ~crc & 0xffffffff;\n}\n\ndouble\ntimeofday()\n{\n  struct timeval tv;\n#ifdef CLOCK_MONOTONIC\n  struct timespec tp;\n\n  if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {\n    return (double)tp.tv_sec + (double)tp.tv_nsec * 1e-9;\n  }\n#endif\n  gettimeofday(&tv, NULL);\n  return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;\n}\n\nuint64_t\ntimeofday_ms()\n{\n  struct timeval tv;\n  gettimeofday(&tv, NULL);\n  return (uint64_t)tv.tv_sec*1e3 + (uint64_t)tv.tv_usec*1e-3;\n}\n"
  },
  {
    "path": "ext/util.h",
    "content": "#if !defined(__util_h__)\n#define __util_h__\n\n#include <stdint.h>\n\n#if defined(_MEMPROF_DEBUG)\n#include <stdio.h>\n#define dbg_printf(...) do {\\\n  fprintf(stderr, \"%s:%d  \", __FILE__, __LINE__);\\\n  fprintf(stderr, __VA_ARGS__);\\\n  } while (0)\n#else\n#define dbg_printf(...)\n#endif\n\n#define ASSERT_ON_COMPILE(pred) \\\n      switch(0){case 0:case pred:;}\n\nstruct memprof_config {\n  void *gc_sweep;\n  size_t gc_sweep_size;\n\n  void *finalize_list;\n  size_t finalize_list_size;\n\n  void *rb_gc_force_recycle;\n  size_t rb_gc_force_recycle_size;\n\n  void *freelist;\n  void *classname;\n  void *add_freelist;\n\n  void *rb_mark_table_add_filename;\n\n  void *bm_mark;\n  void *blk_free;\n  void *thread_mark;\n\n  void *heaps;\n  void *heaps_used;\n  void *finalizer_table;\n\n  size_t sizeof_RVALUE;\n  size_t sizeof_heaps_slot;\n\n  size_t offset_heaps_slot_limit;\n  size_t offset_heaps_slot_slot;\n\n  size_t offset_BLOCK_body;\n  size_t offset_BLOCK_var;\n  size_t offset_BLOCK_cref;\n  size_t offset_BLOCK_prev;\n  size_t offset_BLOCK_self;\n  size_t offset_BLOCK_klass;\n  size_t offset_BLOCK_wrapper;\n  size_t offset_BLOCK_orig_thread;\n  size_t offset_BLOCK_block_obj;\n  size_t offset_BLOCK_scope;\n  size_t offset_BLOCK_dyna_vars;\n\n  size_t offset_METHOD_klass;\n  size_t offset_METHOD_rklass;\n  size_t offset_METHOD_recv;\n  size_t offset_METHOD_id;\n  size_t offset_METHOD_oid;\n  size_t offset_METHOD_body;\n\n  size_t pagesize;\n};\n\n/* This is the CRC function used by GNU. Stripped executables may contain a\n * section .gnu_debuglink which holds the name of an elf object with debug\n * information and a checksum.\n */\nunsigned long\ngnu_debuglink_crc32 (unsigned long crc, unsigned char *buf, size_t len);\n\n/* Copy of timeofday() implementation inside ruby 1.8, used w/ thread state */\ndouble\ntimeofday();\n\n/* Use this function for time tracking. It will (interally) try to use an\n * appropriately granual timing function.\n */\nuint64_t\ntimeofday_ms();\n\n#define TVAL_TO_INT64(tv) ((int64_t)tv.tv_sec*1e3 + (int64_t)tv.tv_usec*1e-3)\n#endif\n"
  },
  {
    "path": "ext/x86_64.c",
    "content": "#if defined (_ARCH_x86_64_)\n\n#include <assert.h>\n#include <stdint.h>\n#include <string.h>\n\n#include \"arch.h\"\n#include \"x86_gen.h\"\n#include \"util.h\"\n\n/*\n * inline_st1_tramp - inline stage 1 trampoline\n *\n * This is the stage 1 inline trampoline that will replace the mov instruction\n * that updates freelist from the inlined function add_freelist.\n *\n * Note that the mov instruction is 7 bytes wide, so this trampoline needs two\n * bytes of NOPs to keep it 7 bytes wide.\n *\n * In order to use this structure, you must set the displacement field to a\n * 32bit displacement from the next instruction to the stage 2 trampoline.\n *\n * TODO replace the 2, 1 byte NOPs with a wider 16bit NOP.\n *\n * Original code:\n *\n *  mov REGISTER, freelist  # update the head of the freelist\n *\n *  size = 7 bytes\n *\n * Code after tramp:\n *\n *  jmp 0xfeedface(%rip)    # jump to stage 2 trampoline\n *  nop                     # 1 byte NOP pad\n *  nop                     # 1 byte NOP pad\n *\n *  size = 7 bytes\n */\nstruct inline_st1_tramp {\n  unsigned char jmp;\n  int32_t displacement;\n  unsigned char pad[2];\n} __attribute__((__packed__)) inline_st1_tramp = {\n  .jmp  = 0xe9,\n  .displacement = 0,\n  .pad = {0x90, 0x90},\n};\n\n/*\n * inline_st1_base - inline stage 1 base instruction\n *\n * This structure is designed to be \"laid onto\" a piece of memory to ease the\n * parsing, modification, and length calculation of the original instruction\n * that will be overwritten with a jmp to the stage 2 trampoline.\n *\n * In order to use this structure, you must set the displacement, rex, and\n * rex bytes to accurately represent the original instruction.\n */\nstruct inline_st1_base {\n  unsigned char rex;\n  unsigned char mov;\n  unsigned char src_reg;\n  int32_t displacement;\n} __attribute__((__packed__)) inline_st1_mov = {\n  .rex = 0,\n  .mov = 0x89,\n  .src_reg = 0,\n  .displacement = 0\n};\n\n/*\n * arch_check_ins - architecture specific instruction check\n *\n * This function checks the opcodes at a specific adderss to see if\n * they could be a move instruction.\n *\n * Returns 1 if the address matches a mov, 0 otherwise.\n */\nstatic int\narch_check_ins(struct inline_st1_base *base)\n{\n  assert(base != NULL);\n\n  /* is it a mov instruction? */\n  if (base->mov == 0x89 &&\n\n      /* maybe. read the REX byte to find out for sure */\n      (base->rex == 0x48 ||\n       base->rex == 0x4c)) {\n\n      /* success */\n      return 1;\n  }\n\n  return 0;\n}\n\n/*\n * arch_insert_inline_st2_tramp - architecture specific stage 2 tramp insert\n *\n * Given:\n *    - addr - The base address of an instruction sequence.\n *\n *    - marker - This is the marker to search for which will indicate that the\n *      instruction sequence has been located.\n *\n *    - trampoline - The address of the handler to redirect execution to.\n *\n *    - table_entry - Address of where the stage 2 trampoline code will reside\n *\n * This function will:\n *    Insert and setup the stage 1 and stage 2 trampolines if addr points to an\n *    instruction that could be from the inlined add_freelist function.\n *\n * This function returns 1 on failure and 0 on success.\n */\nint\narch_insert_inline_st2_tramp(void *addr, void *marker, void *trampoline, void *table_entry)\n{\n  assert(addr != NULL);\n  assert(marker != NULL);\n  assert(trampoline != NULL);\n  assert(table_entry != NULL);\n\n  struct inline_st1_base *base = addr;\n  struct inline_tramp_st2_entry *entry = table_entry;\n\n  ASSERT_ON_COMPILE(sizeof(struct inline_st1_base) ==\n         sizeof(struct inline_st1_tramp));\n\n  if (!arch_check_ins(base))\n    return 1;\n\n  /* Sanity check. Ensure that the displacement from freelist to the next\n   * instruction matches the mov_target. If so, we know this mov is\n   * updating freelist.\n   */\n  if (marker - (void *)(base + 1) == base->displacement) {\n    /* Before the stage 1 trampoline gets written, we need to generate\n     * the code for the stage 2 trampoline. Let's copy over the REX byte\n     * and the byte which mentions the source register into the stage 2\n     * trampoline.\n     */\n    default_inline_st2_tramp.rex = base->rex;\n    default_inline_st2_tramp.src_reg = base->src_reg;\n\n    /* Setup the stage 1 trampoline. Calculate the displacement to\n     * the stage 2 trampoline from the next instruction.\n     *\n     * REMEMBER!!!! The next instruction will be NOP after our stage 1\n     * trampoline is written. This is 5 bytes into the structure, even\n     * though the original instruction we overwrote was 7 bytes.\n     */\n    inline_st1_tramp.displacement = table_entry - (void *)(addr + 5);\n\n    copy_instructions(addr, &inline_st1_tramp, sizeof(inline_st1_tramp));\n\n    /* Finish setting up the stage 2 trampoline. */\n\n    /* calculate the displacement to freelist from the next instruction.\n     *\n     * This is used to replicate the original instruction we overwrote.\n     */\n    default_inline_st2_tramp.mov_displacement = marker - (void *)&(entry->frame);\n\n    /* fill in the displacement to freelist from the next instruction.\n     *\n     * This is to arrange for the new value in freelist to be in %rdi, and as such\n     * be the first argument to the C handler. As per the amd64 ABI.\n     */\n    default_inline_st2_tramp.frame.rdi_source_displacement = marker - (void *)&(entry->frame.align_rsp);\n\n    /* jmp back to the instruction after stage 1 trampoline was inserted\n     *\n     * This can be 5 or 7, it doesn't matter. If its 5, we'll hit our 2\n     * NOPS. If its 7, we'll land directly on the next instruction.\n     */\n    default_inline_st2_tramp.jmp_displacement = (addr + sizeof(*base)) -\n                                                (table_entry + sizeof(default_inline_st2_tramp));\n\n    /* write the address of our C level trampoline in to the structure */\n    default_inline_st2_tramp.frame.addr = trampoline;\n\n    memcpy(table_entry, &default_inline_st2_tramp, sizeof(default_inline_st2_tramp));\n\n    return 0;\n  }\n\n  return 1;\n}\n#endif\n"
  },
  {
    "path": "ext/x86_64.h",
    "content": "#if !defined(_x86_64_h_)\n#define _x86_64_h_\n\n#include <stdint.h>\n#include \"arch.h\"\n\n/*\n * tramp_st2_entry - stage 2 trampoline entry\n *\n * This trampoline calls a handler function via the callee saved register %rbx.\n * The handler function is stored in the field 'addr'.\n *\n * A default pre-filled (except addr, of course) version of this trampoline is\n * provided so that the opcodes do not need to be filled in every time it is\n * used. You only need to set the addr field of default_st2_tramp and you are\n * ready to roll.\n *\n * This trampoline is the assembly code:\n *\n * push %rbx                      # save %rbx\n * push %rbp                      # save previous stack frame's %rbp\n * mov  %rsp, %rbp                # update %rbp to be current stack pointer\n * andl 0xFFFFFFFFFFFFFFF0, %rsp  # align stack pointer as per the ABI\n * mov  ADDR, %rbx                # move address of handler into %rbx\n * callq *%rbx                    # call handler\n * pop %rbx                       # restore %rbx\n * leave                          # restore %rbp, move stack pointer back\n * ret                            # return\n */\nstatic struct tramp_st2_entry {\n  unsigned char push_rbx;\n  unsigned char push_rbp;\n  unsigned char save_rsp[3];\n  unsigned char align_rsp[4];\n  unsigned char mov[2];\n  void *addr;\n  unsigned char call[2];\n  unsigned char leave;\n  unsigned char rbx_restore;\n  unsigned char ret;\n} __attribute__((__packed__)) default_st2_tramp = {\n  .push_rbx      = 0x53,\n  .push_rbp      = 0x55,\n  .save_rsp      = {0x48, 0x89, 0xe5},\n  .align_rsp     = {0x48, 0x83, 0xe4, 0xf0},\n  .mov           = {0x48, 0xbb},\n  .addr          = 0,\n  .call          = {0xff, 0xd3},\n  .rbx_restore   = 0x5b,\n  .leave         = 0xc9,\n  .ret           = 0xc3,\n};\n\n/*\n * inline_tramp_st2_entry - stage 2 inline trampoline entry\n *\n * This trampoline calls a handler function via the callee saved register %rbx,\n * The handler function is stored in the field 'addr'.\n *\n * The major difference between this trampoline and the one above is that this\n * trampoline is intended to be used as the target of an 'inline trampoline',\n * that is code is redirected to this and the stack and registers may not be\n * 'ready' for a function call.\n *\n * This trampoline provides space to regenerate the overwritten mov instruction\n * and utmost care must be taken in order to recreate the overwritten\n * instruction.\n *\n * This trampoline is hit with a jmp (NOT A CALL), and as such must take care\n * to jmp back to resume execution.\n *\n * Like the above trampoline, this structure comes with a prefilled entry called\n * default_inline_st2_tramp that has most of the fields prepopulated.\n *\n * To use this structure you must fill in:\n *   - mov_displacement - should be set to the 32bit displacement from the next\n *     instruction (i.e. frame) to freelist. This is used to recreate the\n *     overwritten instruction.\n *\n *   - rdi_source_displacement - should be set to the 32bit displacement from\n *     the next instruction (i.e. push_rbx) to freelist. This is used to load\n *     freelist as the 1st argument to the handler.\n *\n *   - addr - the address of the handler function to call\n *\n *   - jmp_displacement - should be set to the 32bit displacement from the next\n *     instruction to the instruction after the stage 1 trampoline. This is\n *     used to resume execution after the handler has been hit.\n *\n *\n * This structure represents the assembly code:\n *\n * mov    SOURCE_REGISTER,-0x3f90d4a7(%rip)        # update freelist\n *\n * # save caller saved registers here\n *\n * push   %rax\n * push   %rcx\n * push   %rdx\n * push   %rsi\n * push   %rdi\n * push   %r8\n * push   %r9\n * push   %r10\n * push   %r11\n * push   %rbp\n * mov    %rsp,%rbp\n * mov    -0x3f90d4bf(%rip),%rdi        # move freelist into rdi as arg1\n * and    $0xfffffffffffffff0,%rsp      # align stack pointer\n * mov    $0x7ffff65e74e0,%rcx          # put handler function into position\n * callq  *%rcx                         # call handler\n *\n * # restore caller saved registers here\n *\n * leaveq\n * pop    %r11\n * pop    %r10\n * pop    %r9\n * pop    %r8\n * pop    %rdi\n * pop    %rsi\n * pop    %rdx\n * pop    %rcx\n * pop    %rax\n *\n * jmpq   0x433972 <gc_sweep+754>       # jump back\n */\nstatic struct inline_tramp_st2_entry {\n  unsigned char rex;\n  unsigned char mov;\n  unsigned char src_reg;\n  uint32_t mov_displacement;\n\n  struct {\n    /*\n     * XXX xmm0-xmm7 are caller saved, too.\n     */\n    unsigned char push_rax;\n    unsigned char push_rcx;\n    unsigned char push_rdx;\n    unsigned char push_rsi;\n    unsigned char push_rdi;\n    unsigned char push_r8[2];\n    unsigned char push_r9[2];\n    unsigned char push_r10[2];\n    unsigned char push_r11[2];\n\n    unsigned char push_rbp;\n    unsigned char mov_rsp_rbp[3];\n\n    unsigned char mov_rdi[3];\n    uint32_t rdi_source_displacement;\n\n    unsigned char align_rsp[4];\n    unsigned char mov[2];\n    void *addr;\n    unsigned char call[2];\n\n    unsigned char leave;\n\n    unsigned char pop_r11[2];\n    unsigned char pop_r10[2];\n    unsigned char pop_r9[2];\n    unsigned char pop_r8[2];\n    unsigned char pop_rdi;\n    unsigned char pop_rsi;\n    unsigned char pop_rdx;\n    unsigned char pop_rcx;\n    unsigned char pop_rax;\n} __attribute__((__packed__)) frame;\n\n  unsigned char jmp;\n  uint32_t jmp_displacement;\n} __attribute__((__packed__)) default_inline_st2_tramp = {\n  .rex     = 0x48,\n  .mov     = 0x89,\n  .src_reg = 0x05,\n  .mov_displacement = 0,\n\n  .frame = {\n    .push_rax = 0x50,\n    .push_rcx = 0x51,\n    .push_rdx = 0x52,\n    .push_rsi = 0x56,\n    .push_rdi = 0x57,\n    .push_r8 = {0x41, 0x50},\n    .push_r9 = {0x41, 0x51},\n    .push_r10 = {0x41, 0x52},\n    .push_r11 = {0x41, 0x53},\n    .push_rbp = 0x55,\n    .mov_rsp_rbp = {0x48, 0x89, 0xe5},\n    .mov_rdi =  {0x48, 0x8b, 0x3d},\n    .rdi_source_displacement = 0,\n    .align_rsp = {0x48, 0x83, 0xe4, 0xf0},\n    .mov = {0x48, 0xb9},\n    .addr = 0,\n    .call = {0xff, 0xd1},\n    .leave = 0xc9,\n    .pop_r11 = {0x41, 0x5b},\n    .pop_r10 = {0x41, 0x5a},\n    .pop_r9 = {0x41, 0x59},\n    .pop_r8 = {0x41, 0x58},\n    .pop_rdi = 0x5f,\n    .pop_rsi = 0x5e,\n    .pop_rdx = 0x5a,\n    .pop_rcx = 0x59,\n    .pop_rax = 0x58,\n  },\n\n  .jmp  = 0xe9,\n  .jmp_displacement = 0,\n};\n#endif\n"
  },
  {
    "path": "ext/x86_gen.c",
    "content": "#include <assert.h>\n#include <stdint.h>\n#include \"arch.h\"\n#include \"x86_gen.h\"\n\n/*\n * arch_insert_st1_tramp - architecture specific stage 1 trampoline insert\n *\n * Given:\n *      - a start address (start),\n *      - the absolute address of the function to intercept (trampee),\n *      - the absolute address of the code to execute instead (tramp),\n *\n * This function will:\n *    - interpret address start as a struct st1_base,\n *    - check that the instruction at call is actually a call\n *    - if so, check that the target of the call is trampee\n *    - and change the target to tramp\n *\n * Returns 0 on success, 1 otherwise.\n */\nint\narch_insert_st1_tramp(void *start, void *trampee, void *tramp)\n{\n  assert(start != NULL);\n  assert(trampee != NULL);\n  assert(tramp != NULL);\n\n  int32_t fn_addr = 0;\n  int32_t offset = 0;\n  struct st1_base *check = start;\n\n  if (check->call == 0xe8) {\n    fn_addr = check->displacement;\n    if ((trampee - (void *)(check + 1)) == fn_addr) {\n      offset = tramp - (void *)(check + 1);\n      copy_instructions(&check->displacement, &offset, sizeof(offset));\n      return 0;\n    }\n  }\n\n  return 1;\n}\n\n/*\n * arch_get_st2_tramp - architecture specific stage 2 tramp accessor. This\n * function returns a pointer to the default stage 2 trampoline setting size\n * if a non-NULL pointer was passed in.\n */\nvoid *\narch_get_st2_tramp(size_t *size)\n{\n  if (size) {\n    *size = sizeof(default_st2_tramp);\n  }\n\n  return &default_st2_tramp;\n}\n\n/*\n * arch_get_inline_st2_tramp - architecture specific inline stage 2 tramp\n * accessor. This function returns a pointer to the default inline stage 2\n *  trampoline setting size if a non-NULL pointer was passed in.\n */\nvoid *\narch_get_inline_st2_tramp(size_t *size)\n{\n  if (size) {\n    *size = sizeof(default_inline_st2_tramp);\n  }\n\n  return &default_inline_st2_tramp;\n}\n"
  },
  {
    "path": "ext/x86_gen.h",
    "content": "#if !defined(_x86_gen_)\n#define _x86_gen_\n\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/mman.h>\n#include \"arch.h\"\n\n/*\n * st1_base - stage 1 base instruction sequence\n *\n * This struct is intended to be \"laid onto\" a piece of memory to ease the\n * parsing, use, and length calculation of call instructions that use a 32bit\n * displacement.\n *\n * For example:   callq <0xdeadbeef> #rb_newobj\n */\nstatic struct st1_base {\n  unsigned char call;\n  int32_t displacement;\n} __attribute__((__packed__)) st1_mov = {\n  .call         =  0xe8,\n  .displacement =  0,\n};\n\n/*\n * page_align - given an address, return a page aligned form\n *\n * TODO Don't assume page size, get it from sysconf and cache the result\n */\nstatic inline void *\npage_align(void *addr)\n{\n  assert(addr != NULL);\n  return (void *)((size_t)addr & ~(0xFFFF));\n}\n\n/*\n * copy_instructions - copy count bytes from src to dest, taking care to use\n * mprotect to mark the section read/write.\n */\nstatic void\ncopy_instructions(void *dest, void *src, size_t count)\n{\n  assert(dest != NULL);\n  assert(src != NULL);\n\n  void *aligned_addr = page_align(dest);\n\n  /* I add \"+ count\" here to guard against the possibility of the instructions\n   * laying across a page boundary\n   */\n\n  mprotect(aligned_addr, (dest - aligned_addr) + count, PROT_READ|PROT_WRITE|PROT_EXEC);\n  memcpy(dest, src, count);\n\n  /*\n   *  XXX This has to be commented out because setting certian sections to\n   *      readonly (.got.plt, et al.) will cause the rtld to die.\n   *\n   *      There is no way to get the current permissions bits for a page.\n   *\n   *      The way to solve this is:\n   *\n   *        1.) copy_instructions can take a final_permissions mask and each\n   *            overwrite site can put in the 'Right Thing'\n   *\n   *        2.) Each overwrite site can look up the 'Right Thing' in the object\n   *            header and pass it in, ensuring the desired permissions are\n   *            set after.\n   *\n   *  mprotect(aligned_addr, (dest - aligned_addr) + count, PROT_READ|PROT_EXEC);\n   */\n  return;\n}\n#endif\n"
  },
  {
    "path": "lib/memprof/middleware.rb",
    "content": "require File.expand_path('../../memprof', __FILE__)\nmodule Memprof\n  class Middleware\n    def initialize(app, opts = {})\n      @app = app\n      @options = opts\n    end\n    def call(env)\n      ret = nil\n      Memprof.track{\n        ret = @app.call(env)\n        puts\n        puts '-' * 80\n        puts '-' * 80\n        if @options[:force_gc]\n          puts \"Forcing GC....\"\n          GC.start\n        end\n      }\n      ret\n    end\n  end\nend"
  },
  {
    "path": "lib/memprof/signal.rb",
    "content": "begin\n  require File.expand_path('../../memprof', __FILE__)\nrescue LoadError\n  require File.expand_path('../../../ext/memprof', __FILE__)\nend\n\nMemprof.start\nold_handler = trap('URG'){\n  pid = Process.pid\n  fork{\n    GC.start\n    Memprof.dump_all(\"/tmp/memprof-#{pid}-#{Time.now.to_i}.json\")\n    exit!\n  }\n  old_handler.call if old_handler\n}\n"
  },
  {
    "path": "lib/memprof/tracer.rb",
    "content": "begin\n  require File.expand_path('../../memprof', __FILE__)\nrescue LoadError\n  require File.expand_path('../../../ext/memprof', __FILE__)\nend\n\nmodule Memprof\n  # Middleware for tracing requests\n  #\n  #  require 'memprof/tracer'\n  #  config.middleware.use(Memprof::Tracer)\n  class Tracer\n    def initialize(app)\n      @app=app\n    end\n    def call(env)\n      Memprof.trace_filename ||= \"/tmp/memprof_tracer-#{Process.pid}.json\"\n      Memprof.trace_request(env){ @app.call(env) }\n    end\n  end\n\n  # Legacy filter for tracing requests on Rails 2.2\n  #\n  #  require 'memprof/tracer'\n  #  around_filter(Memprof::Filter)\n  module Filter\n    def self.filter(controller)\n      env = controller.request.env\n      info = controller.request.path_parameters\n      Memprof.trace_filename ||= \"/tmp/memprof_tracer-#{Process.pid}.json\"\n      Memprof.trace_request(env.merge('action_controller.request.path_parameters' => info)){ yield }\n    end\n  end\nend\n"
  },
  {
    "path": "memprof.gemspec",
    "content": "spec = Gem::Specification.new do |s|\n  s.name = 'memprof'\n  s.version = '0.3.10'\n  s.summary = 'Ruby Memory Profiler'\n  s.description = \"Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM\"\n  s.homepage = \"http://github.com/ice799/memprof\"\n  s.has_rdoc = false\n  s.authors = [\"Joe Damato\", \"Aman Gupta\", \"Jake Douglas\", \"Rob Benson\"]\n  s.email = [\"joe@memprof.com\", \"aman@memprof.com\", \"jake@memprof.com\"]\n  s.extensions = \"ext/extconf.rb\"\n  s.bindir = 'bin'\n  s.executables << 'memprof'\n  s.files = `git ls-files`.split\n  s.add_dependency('rest-client', '>= 1.4.2')\n  s.add_dependency('term-ansicolor')\nend\n"
  },
  {
    "path": "spec/memprof_spec.rb",
    "content": "require File.dirname(__FILE__) + \"/../ext/memprof\"\n\nrequire 'rubygems'\nrequire 'bacon'\nBacon.summary_on_exit\n\nrequire 'tempfile'\n\ndescribe Memprof do\n  @tempfile = Tempfile.new('memprof_spec')\n\n  def filename\n    @tempfile.path\n  end\n\n  def filedata\n    File.read(filename)\n  end\n\n  before do\n    Memprof.stop\n  end\n\n  should 'print stats to a file' do\n    Memprof.start\n    \"abc\"\n    Memprof.stats(filename)\n\n    filedata.strip.should == \"1 #{__FILE__}:#{__LINE__-3}:String\"\n  end\n\n  should 'allow calling ::stats multiple times' do\n    Memprof.start\n    []\n    Memprof.stats(filename)\n    []\n    Memprof.stats(filename)\n\n    filedata.strip.split(\"\\n\").size.should == 2\n  end\n\n  should 'clear stats after ::stats!' do\n    Memprof.start\n    []\n    Memprof.stats!(filename)\n    Memprof.stats(filename)\n\n    filedata.strip.should.be.empty\n  end\n\n  should 'collect stats via ::track' do\n    Memprof.track(filename) do\n      \"abc\"\n    end\n\n    filedata.should =~ /1 #{__FILE__}:#{__LINE__-3}:String/\n  end\n\n  should 'dump objects as json' do\n    Memprof.start\n    1.23+1\n    Memprof.dump(filename)\n\n    filedata.should =~ /\"file\":\"#{__FILE__}\"/\n    filedata.should =~ /\"line\":#{__LINE__-4}/\n    filedata.should =~ /\"type\":\"float\"/\n    filedata.should =~ /\"data\":2\\.23/\n  end\n\n  should 'raise error when calling ::stats or ::dump without ::start' do\n    lambda{ Memprof.stats }.should.raise(RuntimeError).message.should =~ /Memprof.start/\n    lambda{ Memprof.dump }.should.raise(RuntimeError).message.should =~ /Memprof.start/\n  end\n\n  should 'dump objects created for block' do\n    Memprof.dump(filename) do\n      2.45+1\n    end\n\n    filedata.should =~ /\"data\":3\\.45/\n  end\n\n  should 'dump out the entire heap' do\n    Memprof.stop\n    Memprof.dump_all(filename)\n\n    obj = File.open(filename, 'r').readlines.find do |line|\n      line =~ /\"dump out the entire heap\"/\n    end\n\n    obj.should =~ /\"length\":24/\n    obj.should =~ /\"type\":\"string\"/\n    obj.should =~ /\"_id\":\"0x(\\w+?)\"/\n  end\n\n  should 'dump out the entire heap with tracking info' do\n    Memprof.start\n    @str = \"some random\" + \" string\"\n    Memprof.dump_all(filename)\n\n    obj = File.open(filename, 'r').readlines.find do |line|\n      line =~ /\"some random string\"/\n    end\n\n    obj.should =~ /\"type\":\"string\"/\n    obj.should =~ /\"file\":\".+?memprof_spec.rb\"/\n    obj.should =~ /\"line\":#{__LINE__-9}/\n  end\nend\n\n"
  },
  {
    "path": "spec/memprof_uploader_spec.rb",
    "content": "require 'rubygems'\nrequire 'bacon'\nBacon.summary_on_exit\n\ndescribe \"MemprofUploader\" do\n\n  it \"should display help output with -h\" do\n    output = `ruby bin/memprof -h`\n    output.should =~ /Memprof Uploader/\n    output.should =~ /Usage:/\n    $?.exitstatus.should == 0\n  end\n\n  it \"should fail without a pid being passed\" do\n    output = `ruby bin/memprof -n SomeLabel -k abcdef`\n    output.should =~ /Missing PID!/\n    $?.exitstatus.should == 1\n  end\n\n  it \"should fail without a name being passed\" do\n    output = `ruby bin/memprof -p 123 -k abcdef`\n    output.should =~ /Missing name!/\n    $?.exitstatus.should == 1\n  end\n\n  it \"should fail without an API key\" do\n    output = `ruby bin/memprof -p 123 -n SomeLabel`\n    output.should =~ /Missing API key!/\n    $?.exitstatus.should == 1\n  end\n\n  it \"should fail with an invalid pid\" do\n    output = `ruby bin/memprof -p 99999999 -n Label -k abcdef`\n    output.should =~ Regexp.new(\"No such process 99999999!\")\n    $?.exitstatus.should == 1\n  end\n\n  it \"should fail when the target process does not create a new file within 5 sec\" do\n    pid = fork { sleep 5; exit! }\n    Process.detach(pid)\n    output = `ruby bin/memprof -p #{pid} -n Label -k abcdef`\n    output.should =~ Regexp.new(\"Waiting 5 seconds for process #{pid} to create a new dump...\")\n    output.should =~ Regexp.new(\"Timed out after waiting 5 seconds\")\n    $?.exitstatus.should == 1\n  end\n\n  it \"should WORK and wait for a dump to complete if it's IN_PROGRESS\" do\n    pid = fork {\n      # create a fake file\n      filename = \"/tmp/memprof-#{Process.pid}-#{Time.now.to_i}.json.IN_PROGRESS\"\n      # simulate dump in progress\n      trap(\"URG\") { File.open(filename, \"w\") {|f| f.write(\"foo\") }; sleep 1 }\n      # should get signaled somewhere in here and execute the handler before exiting.\n      sleep 5\n      # rename the file to simulate completion of the dump writeout.\n      File.rename(filename, filename.sub(/\\.IN_PROGRESS/, \"\"))\n      exit!\n      }\n    Process.detach(pid)\n    output = `ruby bin/memprof -p #{pid} -n Label -k abcdef -t`\n    output.should =~ Regexp.new(\"Waiting 5 seconds for process #{pid} to create a new dump...\")\n    output.should =~ Regexp.new(\"Found file /tmp/memprof-#{pid}-\\\\d*.json\\\\.?\\\\w*\")\n    output.should =~ Regexp.new(\"Dump in progress. Waiting 60 seconds for it to complete...\")\n    output.should =~ Regexp.new(\"Finished!\")\n    file = output.slice(Regexp.new(\"/tmp/memprof-#{pid}-\\\\d*.json\\\\.?\\\\w*\"))\n    # make sure both files are gone\n    File.exist?(file).should == false\n    File.exist?(file.sub(/\\.IN_PROGRESS/, \"\") + \".gz\").should == false\n    $?.exitstatus.should == 0\n  end\n\n  it \"should WORK and delete the dump file after it's done, by default\" do\n    pid = fork {\n      require File.dirname(__FILE__) + \"/../lib/memprof/signal\"\n      # should get signaled somewhere in here and execute the handler before exiting.\n      sleep 5\n      exit!\n    }\n    Process.detach(pid)\n    sleep 2\n    output = `ruby bin/memprof -p #{pid} -n TestDump -k abcdef -t`\n    output.should =~ Regexp.new(\"Waiting 5 seconds for process #{pid} to create a new dump...\")\n    output.should =~ Regexp.new(\"Found file /tmp/memprof-#{pid}-\\\\d*.json\\\\.?\\\\w*\")\n    output.should =~ Regexp.new(\"Finished!\")\n    file = output.slice(Regexp.new(\"/tmp/memprof-#{pid}-\\\\d*.json\\\\.?\\\\w*\"))\n    # make sure both files are gone\n    File.exist?(file).should == false\n    File.exist?(file.sub(/\\.IN_PROGRESS/, \"\") + \".gz\").should == false\n    $?.exitstatus.should == 0\n  end\n\n  it \"should WORK and leave the dump file after it's done, with --no-delete\" do\n    pid = fork {\n      require File.dirname(__FILE__) + \"/../lib/memprof/signal\"\n      # should get signaled somewhere in here and execute the handler before exiting.\n      sleep 5\n      exit!\n    }\n    Process.detach(pid)\n    sleep 2\n    output = `ruby bin/memprof -p #{pid} -n TestDump -k abcdef -t --no-delete`\n    output.should =~ Regexp.new(\"Waiting 5 seconds for process #{pid} to create a new dump...\")\n    output.should =~ Regexp.new(\"Found file /tmp/memprof-#{pid}-\\\\d*.json\\\\w*\")\n    output.should =~ Regexp.new(\"Finished!\")\n    file = output.slice(Regexp.new(\"/tmp/memprof-#{pid}-\\\\d*.json\\\\.?\\\\w*\"))\n    # Make sure it deleted the temporary one\n    if file =~ /\\.IN_PROGRESS/\n      File.exist?(file).should == false\n    end\n    # make sure it left the completed one\n    file = file.sub(/\\.IN_PROGRESS/, \"\") + \".gz\"\n    File.exist?(file).should == true\n    File.delete(file)\n    $?.exitstatus.should == 0\n  end\n\nend"
  },
  {
    "path": "spec/tracing_spec.rb",
    "content": "require File.dirname(__FILE__) + \"/../ext/memprof\"\n\nrequire 'rubygems'\nrequire 'bacon'\nBacon.summary_on_exit\n\nrequire 'tempfile'\n\n# XXX must require upfront before tracers are installed\nrequire 'socket'\nrequire 'open-uri'\nbegin; require 'mysql';     rescue LoadError; end\nbegin; require 'memcached'; rescue LoadError; end\n\ndescribe 'Memprof tracers' do\n  @tempfile = Tempfile.new('tracing_spec')\n\n  def filename\n    @tempfile.path\n  end\n\n  def filedata\n    File.read(filename)\n  end\n\n  should 'trace i/o for block' do\n    Memprof.trace(filename) do\n      open(\"http://google.com\").read\n    end\n\n    filedata.should =~ /\"read\":\\{\"calls\":\\d+/\n    filedata.should =~ /\"write\":\\{\"calls\":\\d+/\n    filedata.should =~ /\"connect\":\\{\"calls\":\\d+/\n  end\n\n  should 'trace select for block' do\n    Memprof.trace(filename) do\n      select(nil, nil, nil, 0.15)\n    end\n\n    filedata.should =~ /\"select\":\\{\"calls\":1,\"time\":1[567]\\d/\n    time = filedata[/\"select\":\\{\"calls\":\\d+,\"time\":([\\d.]+)/, 1].to_f\n    time.should.be.close(150, 10)\n  end\n\n  should 'trace objects created for block' do\n    Memprof.trace(filename) do\n      10.times{1.1+1.2}\n    end\n\n    filedata.should =~ /\"float\":10/\n  end\n\n  should 'trace gc runs for block' do\n    Memprof.trace(filename) do\n      10.times{GC.start}\n    end\n\n    filedata.should =~ /\"gc\":\\{\"calls\":10,\"time\":[\\d.]+/\n  end\n\n  should 'trace memory allocation for block' do\n    Memprof.trace(filename) do\n      10.times{ \"abc\" << \"def\" }\n    end\n\n    filedata.should =~ /\"malloc\":\\{\"calls\":10/\n    filedata.should =~ /\"realloc\":\\{\"calls\":10/\n  end\n\n  if defined? Mysql\n    begin\n      conn = Mysql.connect('localhost', 'root')\n\n      should 'trace mysql calls for block' do\n        Memprof.trace(filename) do\n          5.times{ conn.query(\"select sleep(0.05)\") }\n        end\n\n        filedata.should =~ /\"mysql\":\\{\"queries\":5,\"time\":([\\d.]+)/\n        time = filedata[/\"mysql\":\\{\"queries\":5,\"time\":([\\d.]+)/, 1].to_f\n        time.should.be.close(250, 25)\n      end\n    rescue Mysql::Error => e\n      raise unless e.message =~ /connect/\n    end\n  end\n\n  if defined? Memcached\n    begin\n      conn = Memcached.new(\"localhost:11211\", :show_backtraces => true)\n      conn.stats\n\n      should 'trace memcached calls for block' do\n        Memprof.trace(filename) do\n          conn.delete(\"memprof\") rescue nil\n          conn.get(\"memprof\") rescue nil\n          conn.set(\"memprof\", \"is cool\")\n          conn.get(\"memprof\")\n        end\n\n        filedata.should =~ /\"memcache\":\\{\"get\":\\{\"calls\":2,\"responses\":\\{\"success\":1,\"notfound\":1/\n        filedata.should =~ /\"set\":\\{\"calls\":1,\"responses\":\\{\"success\":1/\n      end\n    rescue Memcached::SomeErrorsWereReported\n    end\n  end\nend\n\ndescribe 'Memprof request tracing' do\n  @tempfile = Tempfile.new('tracing_spec')\n\n  def filename\n    @tempfile.path\n  end\n\n  def filedata\n    File.read(filename)\n  end\n\n  should 'trace request env' do\n    env = {\"REQUEST_PATH\" => \"value\"}\n\n    Memprof.trace_filename = filename\n    Memprof.trace_filename.should == filename\n\n    Memprof.trace_request(env) do\n    end\n\n    Memprof.trace_filename = nil\n    Memprof.trace_filename.should.be.nil\n\n    filedata.should =~ /\"REQUEST_PATH\":\"value\"/\n  end\nend\n"
  }
]