diff options
Diffstat (limited to 'debug.c')
| -rw-r--r-- | debug.c | 654 |
1 files changed, 579 insertions, 75 deletions
@@ -9,14 +9,33 @@ **********************************************************************/ -#include "ruby/ruby.h" +#include "ruby/internal/config.h" + +#include <stdio.h> + +#include "eval_intern.h" +#include "encindex.h" +#include "id.h" +#include "internal/signal.h" #include "ruby/encoding.h" +#include "ruby/io.h" +#include "ruby/ruby.h" #include "ruby/util.h" -#include "vm_debug.h" -#include "eval_intern.h" -#include "vm_core.h" #include "symbol.h" -#include "id.h" +#include "vm_core.h" +#include "vm_debug.h" +#include "vm_callinfo.h" +#include "ruby/thread_native.h" +#include "ractor_core.h" + +/* This is the only place struct RIMemo is actually used */ +struct RIMemo { + VALUE flags; + VALUE v0; + VALUE v1; + VALUE v2; + VALUE v3; +}; /* for gdb */ const union { @@ -27,31 +46,36 @@ const union { enum ruby_method_ids method_ids; enum ruby_id_types id_types; enum ruby_fl_type fl_types; + enum ruby_fl_ushift fl_ushift; enum ruby_encoding_consts encoding_consts; enum ruby_coderange_type enc_coderange_types; enum ruby_econv_flag_type econv_flag_types; + rb_econv_result_t econv_result; + enum ruby_preserved_encindex encoding_index; enum ruby_robject_flags robject_flags; enum ruby_rmodule_flags rmodule_flags; enum ruby_rstring_flags rstring_flags; enum ruby_rarray_flags rarray_flags; + enum ruby_rarray_consts rarray_consts; + enum rbimpl_typeddata_flags rtypeddata_consts; enum { - RUBY_FMODE_READABLE = FMODE_READABLE, - RUBY_FMODE_WRITABLE = FMODE_WRITABLE, - RUBY_FMODE_READWRITE = FMODE_READWRITE, - RUBY_FMODE_BINMODE = FMODE_BINMODE, - RUBY_FMODE_SYNC = FMODE_SYNC, - RUBY_FMODE_TTY = FMODE_TTY, - RUBY_FMODE_DUPLEX = FMODE_DUPLEX, - RUBY_FMODE_APPEND = FMODE_APPEND, - RUBY_FMODE_CREATE = FMODE_CREATE, - RUBY_FMODE_NOREVLOOKUP = 0x00000100, - RUBY_FMODE_TRUNC = FMODE_TRUNC, - RUBY_FMODE_TEXTMODE = FMODE_TEXTMODE, - RUBY_FMODE_PREP = 0x00010000, - RUBY_FMODE_SETENC_BY_BOM = FMODE_SETENC_BY_BOM, - RUBY_FMODE_UNIX = 0x00200000, - RUBY_FMODE_INET = 0x00400000, - RUBY_FMODE_INET6 = 0x00800000, + RUBY_FMODE_READABLE = FMODE_READABLE, + RUBY_FMODE_WRITABLE = FMODE_WRITABLE, + RUBY_FMODE_READWRITE = FMODE_READWRITE, + RUBY_FMODE_BINMODE = FMODE_BINMODE, + RUBY_FMODE_SYNC = FMODE_SYNC, + RUBY_FMODE_TTY = FMODE_TTY, + RUBY_FMODE_DUPLEX = FMODE_DUPLEX, + RUBY_FMODE_APPEND = FMODE_APPEND, + RUBY_FMODE_CREATE = FMODE_CREATE, + RUBY_FMODE_NOREVLOOKUP = 0x00000100, + RUBY_FMODE_TRUNC = FMODE_TRUNC, + RUBY_FMODE_TEXTMODE = FMODE_TEXTMODE, + RUBY_FMODE_EXTERNAL = 0x00010000, + RUBY_FMODE_SETENC_BY_BOM = FMODE_SETENC_BY_BOM, + RUBY_FMODE_UNIX = 0x00200000, + RUBY_FMODE_INET = 0x00400000, + RUBY_FMODE_INET6 = 0x00800000, RUBY_NODE_TYPESHIFT = NODE_TYPESHIFT, RUBY_NODE_TYPEMASK = NODE_TYPEMASK, @@ -59,9 +83,9 @@ const union { RUBY_NODE_FL_NEWLINE = NODE_FL_NEWLINE } various; union { - enum imemo_type types; - enum {RUBY_IMEMO_MASK = IMEMO_MASK} mask; - struct RIMemo *ptr; + enum imemo_type types; + enum {RUBY_IMEMO_MASK = IMEMO_MASK} mask; + struct RIMemo *ptr; } imemo; struct RSymbol *symbol_ptr; enum vm_call_flag_bits vm_call_flags; @@ -73,9 +97,9 @@ int ruby_debug_print_indent(int level, int debug_level, int indent_level) { if (level < debug_level) { - fprintf(stderr, "%*s", indent_level, ""); - fflush(stderr); - return TRUE; + fprintf(stderr, "%*s", indent_level, ""); + fflush(stderr); + return TRUE; } return FALSE; } @@ -89,17 +113,17 @@ ruby_debug_printf(const char *format, ...) va_end(ap); } -#include "gc.h" +#include "internal/gc.h" VALUE ruby_debug_print_value(int level, int debug_level, const char *header, VALUE obj) { if (level < debug_level) { - char buff[0x100]; - rb_raw_obj_info(buff, 0x100, obj); + char buff[0x100]; + rb_raw_obj_info(buff, 0x100, obj); - fprintf(stderr, "DBG> %s: %s\n", header, buff); - fflush(stderr); + fprintf(stderr, "DBG> %s: %s\n", header, buff); + fflush(stderr); } return obj; } @@ -114,8 +138,8 @@ ID ruby_debug_print_id(int level, int debug_level, const char *header, ID id) { if (level < debug_level) { - fprintf(stderr, "DBG> %s: %s\n", header, rb_id2name(id)); - fflush(stderr); + fprintf(stderr, "DBG> %s: %s\n", header, rb_id2name(id)); + fflush(stderr); } return id; } @@ -124,28 +148,35 @@ NODE * ruby_debug_print_node(int level, int debug_level, const char *header, const NODE *node) { if (level < debug_level) { - fprintf(stderr, "DBG> %s: %s (%u)\n", header, - ruby_node_name(nd_type(node)), nd_line(node)); + fprintf(stderr, "DBG> %s: %s (id: %d, line: %d, location: (%d,%d)-(%d,%d))\n", + header, ruby_node_name(nd_type(node)), nd_node_id(node), nd_line(node), + nd_first_lineno(node), nd_first_column(node), + nd_last_lineno(node), nd_last_column(node)); } return (NODE *)node; } void +ruby_debug_print_n(const NODE *node) +{ + ruby_debug_print_node(0, 1, "", node); +} + +void ruby_debug_breakpoint(void) { /* */ } #if defined _WIN32 -# if RUBY_MSVCRT_VERSION >= 80 extern int ruby_w32_rtc_error; -# endif #endif #if defined _WIN32 || defined __CYGWIN__ #include <windows.h> UINT ruby_w32_codepage[2]; #endif extern int ruby_rgengc_debug; +extern int ruby_on_ci; int ruby_env_debug_option(const char *str, int len, void *arg) @@ -153,12 +184,12 @@ ruby_env_debug_option(const char *str, int len, void *arg) int ov; size_t retlen; unsigned long n; +#define NAME_MATCH(name) (len == sizeof(name) - 1 && strncmp(str, (name), len) == 0) #define SET_WHEN(name, var, val) do { \ - if (len == sizeof(name) - 1 && \ - strncmp(str, (name), len) == 0) { \ - (var) = (val); \ - return 1; \ - } \ + if (NAME_MATCH(name)) { \ + (var) = (val); \ + return 1; \ + } \ } while (0) #define NAME_MATCH_VALUE(name) \ ((size_t)len >= sizeof(name)-1 && \ @@ -167,46 +198,45 @@ ruby_env_debug_option(const char *str, int len, void *arg) (str[sizeof(name)-1] == '=' && \ (str += sizeof(name), len -= sizeof(name), 1)))) #define SET_UINT(val) do { \ - n = ruby_scan_digits(str, len, 10, &retlen, &ov); \ - if (!ov && retlen) { \ - val = (unsigned int)n; \ - } \ - str += retlen; \ - len -= retlen; \ + n = ruby_scan_digits(str, len, 10, &retlen, &ov); \ + if (!ov && retlen) { \ + val = (unsigned int)n; \ + } \ + str += retlen; \ + len -= retlen; \ } while (0) #define SET_UINT_LIST(name, vals, num) do { \ - int i; \ - for (i = 0; i < (num); ++i) { \ - SET_UINT((vals)[i]); \ - if (!len || *str != ':') break; \ - ++str; \ - --len; \ - } \ - if (len > 0) { \ - fprintf(stderr, "ignored "name" option: `%.*s'\n", len, str); \ - } \ + int i; \ + for (i = 0; i < (num); ++i) { \ + SET_UINT((vals)[i]); \ + if (!len || *str != ':') break; \ + ++str; \ + --len; \ + } \ + if (len > 0) { \ + fprintf(stderr, "ignored "name" option: '%.*s'\n", len, str); \ + } \ } while (0) #define SET_WHEN_UINT(name, vals, num, req) \ - if (NAME_MATCH_VALUE(name)) SET_UINT_LIST(name, vals, num); + if (NAME_MATCH_VALUE(name)) { \ + if (!len) req; \ + else SET_UINT_LIST(name, vals, num); \ + return 1; \ + } - SET_WHEN("gc_stress", *ruby_initial_gc_stress_ptr, Qtrue); - SET_WHEN("core", ruby_enable_coredump, 1); - if (NAME_MATCH_VALUE("rgengc")) { - if (!len) ruby_rgengc_debug = 1; - else SET_UINT_LIST("rgengc", &ruby_rgengc_debug, 1); - return 1; + if (NAME_MATCH("gc_stress")) { + rb_gc_initial_stress_set(Qtrue); + return 1; } + SET_WHEN("core", ruby_enable_coredump, 1); + SET_WHEN("ci", ruby_on_ci, 1); + SET_WHEN_UINT("rgengc", &ruby_rgengc_debug, 1, ruby_rgengc_debug = 1); #if defined _WIN32 -# if RUBY_MSVCRT_VERSION >= 80 SET_WHEN("rtc_error", ruby_w32_rtc_error, 1); -# endif #endif #if defined _WIN32 || defined __CYGWIN__ - if (NAME_MATCH_VALUE("codepage")) { - if (!len) fprintf(stderr, "missing codepage argument"); - else SET_UINT_LIST("codepage", ruby_w32_codepage, numberof(ruby_w32_codepage)); - return 1; - } + SET_WHEN_UINT("codepage", ruby_w32_codepage, numberof(ruby_w32_codepage), + fprintf(stderr, "missing codepage argument")); #endif return 0; } @@ -215,12 +245,486 @@ static void set_debug_option(const char *str, int len, void *arg) { if (!ruby_env_debug_option(str, len, arg)) { - fprintf(stderr, "unexpected debug option: %.*s\n", len, str); + fprintf(stderr, "unexpected debug option: %.*s\n", len, str); } } +#if USE_RUBY_DEBUG_LOG +static void setup_debug_log(void); +#else +#define setup_debug_log() +#endif + void ruby_set_debug_option(const char *str) { ruby_each_words(str, set_debug_option, 0); + setup_debug_log(); +} + +#if USE_RUBY_DEBUG_LOG + +// RUBY_DEBUG_LOG features +// See vm_debug.h comments for details. + +#define MAX_DEBUG_LOG 0x1000 +#define MAX_DEBUG_LOG_MESSAGE_LEN 0x0200 +#define MAX_DEBUG_LOG_FILTER_LEN 0x0020 +#define MAX_DEBUG_LOG_FILTER_NUM 0x0010 + +enum ruby_debug_log_mode ruby_debug_log_mode; + +struct debug_log_filter { + enum debug_log_filter_type { + dlf_all, + dlf_file, // "file:..." + dlf_func, // "func:..." + } type; + bool negative; + char str[MAX_DEBUG_LOG_FILTER_LEN]; +}; + +static const char *dlf_type_names[] = { + "all", + "file", + "func", +}; + +#ifdef MAX_PATH +#define DEBUG_LOG_MAX_PATH (MAX_PATH-1) +#else +#define DEBUG_LOG_MAX_PATH 255 +#endif + +static struct { + char *mem; + unsigned int cnt; + struct debug_log_filter filters[MAX_DEBUG_LOG_FILTER_NUM]; + unsigned int filters_num; + bool show_pid; + rb_nativethread_lock_t lock; + char output_file[DEBUG_LOG_MAX_PATH+1]; + FILE *output; +} debug_log; + +static char * +RUBY_DEBUG_LOG_MEM_ENTRY(unsigned int index) +{ + return &debug_log.mem[MAX_DEBUG_LOG_MESSAGE_LEN * index]; +} + +static enum debug_log_filter_type +filter_type(const char *str, int *skiplen) +{ + if (strncmp(str, "file:", 5) == 0) { + *skiplen = 5; + return dlf_file; + } + else if(strncmp(str, "func:", 5) == 0) { + *skiplen = 5; + return dlf_func; + } + else { + *skiplen = 0; + return dlf_all; + } +} + +static void +setup_debug_log_filter(void) +{ + const char *filter_config = getenv("RUBY_DEBUG_LOG_FILTER"); + + if (filter_config && strlen(filter_config) > 0) { + unsigned int i; + for (i=0; i<MAX_DEBUG_LOG_FILTER_NUM && filter_config; i++) { + size_t len; + const char *str = filter_config; + const char *p; + + if ((p = strchr(str, ',')) == NULL) { + len = strlen(str); + filter_config = NULL; + } + else { + len = p - str - 1; // 1 is ',' + filter_config = p + 1; + } + + // positive/negative + if (*str == '-') { + debug_log.filters[i].negative = true; + str++; + } + else if (*str == '+') { + // negative is false on default. + str++; + } + + // type + int skiplen; + debug_log.filters[i].type = filter_type(str, &skiplen); + len -= skiplen; + + if (len >= MAX_DEBUG_LOG_FILTER_LEN) { + fprintf(stderr, "too long: %s (max:%d)\n", str, MAX_DEBUG_LOG_FILTER_LEN - 1); + exit(1); + } + + // body + strncpy(debug_log.filters[i].str, str + skiplen, len); + debug_log.filters[i].str[len] = 0; + } + debug_log.filters_num = i; + + for (i=0; i<debug_log.filters_num; i++) { + fprintf(stderr, "RUBY_DEBUG_LOG_FILTER[%d]=%s (%s%s)\n", i, + debug_log.filters[i].str, + debug_log.filters[i].negative ? "-" : "", + dlf_type_names[debug_log.filters[i].type]); + } + } +} + +static void +setup_debug_log(void) +{ + // check RUBY_DEBUG_LOG + const char *log_config = getenv("RUBY_DEBUG_LOG"); + if (log_config && strlen(log_config) > 0) { + if (strcmp(log_config, "mem") == 0) { + debug_log.mem = (char *)malloc(MAX_DEBUG_LOG * MAX_DEBUG_LOG_MESSAGE_LEN); + if (debug_log.mem == NULL) { + fprintf(stderr, "setup_debug_log failed (can't allocate memory)\n"); + exit(1); + } + ruby_debug_log_mode |= ruby_debug_log_memory; + } + else if (strcmp(log_config, "stderr") == 0) { + ruby_debug_log_mode |= ruby_debug_log_stderr; + } + else { + ruby_debug_log_mode |= ruby_debug_log_file; + + // pid extension with %p + unsigned long len = strlen(log_config); + + for (unsigned long i=0, j=0; i<len; i++) { + const char c = log_config[i]; + + if (c == '%') { + i++; + switch (log_config[i]) { + case '%': + debug_log.output_file[j++] = '%'; + break; + case 'p': + snprintf(debug_log.output_file + j, DEBUG_LOG_MAX_PATH - j, "%d", getpid()); + j = strlen(debug_log.output_file); + break; + default: + fprintf(stderr, "can not parse RUBY_DEBUG_LOG filename: %s\n", log_config); + exit(1); + } + } + else { + debug_log.output_file[j++] = c; + } + + if (j >= DEBUG_LOG_MAX_PATH) { + fprintf(stderr, "RUBY_DEBUG_LOG=%s is too long\n", log_config); + exit(1); + } + } + + if ((debug_log.output = fopen(debug_log.output_file, "w")) == NULL) { + fprintf(stderr, "can not open %s for RUBY_DEBUG_LOG\n", log_config); + exit(1); + } + setvbuf(debug_log.output, NULL, _IONBF, 0); + } + + fprintf(stderr, "RUBY_DEBUG_LOG=%s %s%s%s\n", log_config, + (ruby_debug_log_mode & ruby_debug_log_memory) ? "[mem]" : "", + (ruby_debug_log_mode & ruby_debug_log_stderr) ? "[stderr]" : "", + (ruby_debug_log_mode & ruby_debug_log_file) ? "[file]" : ""); + if (debug_log.output_file[0]) { + fprintf(stderr, "RUBY_DEBUG_LOG filename=%s\n", debug_log.output_file); + } + + rb_nativethread_lock_initialize(&debug_log.lock); + + setup_debug_log_filter(); + + if (getenv("RUBY_DEBUG_LOG_PID")) { + debug_log.show_pid = true; + } + } +} + +static bool +check_filter(const char *str, const struct debug_log_filter *filter, bool *state) +{ + if (filter->negative) { + if (strstr(str, filter->str) == NULL) { + *state = true; + return false; + } + else { + *state = false; + return true; + } + } + else { + if (strstr(str, filter->str) != NULL) { + *state = true; + return true; + } + else { + *state = false; + return false; + } + } +} + +// +// RUBY_DEBUG_LOG_FILTER=-foo,-bar,baz,boo +// returns true if +// (func_name or file_name) doesn't contain foo +// and +// (func_name or file_name) doesn't contain bar +// and +// (func_name or file_name) contains baz or boo +// +// RUBY_DEBUG_LOG_FILTER=foo,bar,-baz,-boo +// returns true if +// (func_name or file_name) contains foo or bar +// or +// (func_name or file_name) doesn't contain baz and +// (func_name or file_name) doesn't contain boo and +// +// You can specify "file:" (ex file:foo) or "func:" (ex func:foo) +// prefixes to specify the filter for. +// +bool +ruby_debug_log_filter(const char *func_name, const char *file_name) +{ + if (debug_log.filters_num > 0) { + bool state = false; + + for (unsigned int i = 0; i<debug_log.filters_num; i++) { + const struct debug_log_filter *filter = &debug_log.filters[i]; + + switch (filter->type) { + case dlf_all: + if (check_filter(func_name, filter, &state)) return state; + if (check_filter(file_name, filter, &state)) return state; + break; + case dlf_func: + if (check_filter(func_name, filter, &state)) return state; + break; + case dlf_file: + if (check_filter(file_name, filter, &state)) return state; + break; + } + } + return state; + } + else { + return true; + } +} + +static const char * +pretty_filename(const char *path) +{ + // basename is one idea. + const char *s; + while ((s = strchr(path, '/')) != NULL) { + path = s+1; + } + return path; +} + +#undef ruby_debug_log +void +ruby_debug_log(const char *file, int line, const char *func_name, const char *fmt, ...) +{ + char buff[MAX_DEBUG_LOG_MESSAGE_LEN] = {0}; + int len = 0; + int r = 0; + + if (debug_log.show_pid) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN, "pid:%d\t", getpid()); + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + + // message title + if (func_name && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN, "%s\t", func_name); + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + + // message + if (fmt && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + va_list args; + va_start(args, fmt); + r = vsnprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, fmt, args); + va_end(args); + if (r < 0) rb_bug("ruby_debug_log vsnprintf() returns %d", r); + len += r; + } + + // optional information + + // C location + if (file && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN, "\t%s:%d", pretty_filename(file), line); + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + + rb_execution_context_t *ec = rb_current_execution_context(false); + + // Ruby location + int ruby_line; + const char *ruby_file = ec ? rb_source_location_cstr(&ruby_line) : NULL; + + if (len < MAX_DEBUG_LOG_MESSAGE_LEN) { + if (ruby_file) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\t%s:%d", pretty_filename(ruby_file), ruby_line); + } + else { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\t"); + } + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + +#ifdef RUBY_NT_SERIAL + // native thread information + if (len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tnt:%d", ruby_nt_serial); + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } +#endif + + if (ec) { + rb_thread_t *th = ec ? rb_ec_thread_ptr(ec) : NULL; + + // ractor information + if (ruby_single_main_ractor == NULL) { + rb_ractor_t *cr = th ? th->ractor : NULL; + rb_vm_t *vm = GET_VM(); + + if (r && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tr:#%d/%u (%u)", + cr ? (int)rb_ractor_id(cr) : -1, vm->ractor.cnt, vm->ractor.sched.running_cnt); + + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + } + + // thread information + if (th && r && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + rb_execution_context_t *rec = th->ractor ? th->ractor->threads.running_ec : NULL; + const rb_thread_t *rth = rec ? rec->thread_ptr : NULL; + const rb_thread_t *sth = th->ractor ? th->ractor->threads.sched.running : NULL; + + if (rth != th || sth != th) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tth:%u (rth:%d,sth:%d)", + rb_th_serial(th), rth ? (int)rb_th_serial(rth) : -1, sth ? (int)rb_th_serial(sth) : -1); + } + else { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tth:%u", rb_th_serial(th)); + } + if (r < 0) rb_bug("ruby_debug_log returns %d", r); + len += r; + } + } + + rb_nativethread_lock_lock(&debug_log.lock); + { + unsigned int cnt = debug_log.cnt++; + + if (ruby_debug_log_mode & ruby_debug_log_memory) { + unsigned int index = cnt % MAX_DEBUG_LOG; + char *dst = RUBY_DEBUG_LOG_MEM_ENTRY(index); + strncpy(dst, buff, MAX_DEBUG_LOG_MESSAGE_LEN); + } + if (ruby_debug_log_mode & ruby_debug_log_stderr) { + fprintf(stderr, "%4u: %s\n", cnt, buff); + } + if (ruby_debug_log_mode & ruby_debug_log_file) { + fprintf(debug_log.output, "%u\t%s\n", cnt, buff); + } + } + rb_nativethread_lock_unlock(&debug_log.lock); +} + +// for debugger +static void +debug_log_dump(FILE *out, unsigned int n) +{ + if (ruby_debug_log_mode & ruby_debug_log_memory) { + unsigned int size = debug_log.cnt > MAX_DEBUG_LOG ? MAX_DEBUG_LOG : debug_log.cnt; + unsigned int current_index = debug_log.cnt % MAX_DEBUG_LOG; + if (n == 0) n = size; + if (n > size) n = size; + + for (unsigned int i=0; i<n; i++) { + int index = current_index - size + i; + if (index < 0) index += MAX_DEBUG_LOG; + VM_ASSERT(index <= MAX_DEBUG_LOG); + const char *mesg = RUBY_DEBUG_LOG_MEM_ENTRY(index); + fprintf(out, "%4u: %s\n", debug_log.cnt - size + i, mesg); + } + } + else { + fprintf(stderr, "RUBY_DEBUG_LOG=mem is not specified."); + } } + +// for debuggers + +void +ruby_debug_log_print(unsigned int n) +{ + debug_log_dump(stderr, n); +} + +void +ruby_debug_log_dump(const char *fname, unsigned int n) +{ + FILE *fp = fopen(fname, "w"); + if (fp == NULL) { + fprintf(stderr, "can't open %s. give up.\n", fname); + } + else { + debug_log_dump(fp, n); + fclose(fp); + } +} + +#else + +#undef ruby_debug_log +void +ruby_debug_log(const char *file, int line, const char *func_name, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "[%s:%d] %s: ", file, line, func_name); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); +} + +#endif // #if USE_RUBY_DEBUG_LOG |
