diff options
Diffstat (limited to 'debug.c')
| -rw-r--r-- | debug.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/debug.c b/debug.c new file mode 100644 index 0000000000..a5e6ce475a --- /dev/null +++ b/debug.c @@ -0,0 +1,514 @@ +/********************************************************************** + + debug.c - + + $Author$ + created at: 04/08/25 02:31:54 JST + + Copyright (C) 2004-2007 Koichi Sasada + +**********************************************************************/ + +#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 "symbol.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 { + enum ruby_special_consts special_consts; + enum ruby_value_type value_type; + enum ruby_tag_type tag_type; + enum node_type node_type; + 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_robject_consts robject_consts; + enum ruby_rmodule_flags rmodule_flags; + enum ruby_rstring_flags rstring_flags; +#if !USE_RVARGC + enum ruby_rstring_consts rstring_consts; +#endif + enum ruby_rarray_flags rarray_flags; + enum ruby_rarray_consts rarray_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_NODE_TYPESHIFT = NODE_TYPESHIFT, + RUBY_NODE_TYPEMASK = NODE_TYPEMASK, + RUBY_NODE_LSHIFT = NODE_LSHIFT, + RUBY_NODE_FL_NEWLINE = NODE_FL_NEWLINE + } various; + union { + 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; +} ruby_dummy_gdb_enums; + +const SIGNED_VALUE RUBY_NODE_LMASK = NODE_LMASK; + +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; + } + return FALSE; +} + +void +ruby_debug_printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +#include "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); + + fprintf(stderr, "DBG> %s: %s\n", header, buff); + fflush(stderr); + } + return obj; +} + +void +ruby_debug_print_v(VALUE v) +{ + ruby_debug_print_value(0, 1, "", v); +} + +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); + } + return id; +} + +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)); + } + return (NODE *)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) +{ + int ov; + size_t retlen; + unsigned long n; +#define SET_WHEN(name, var, val) do { \ + if (len == sizeof(name) - 1 && \ + strncmp(str, (name), len) == 0) { \ + (var) = (val); \ + return 1; \ + } \ + } while (0) +#define NAME_MATCH_VALUE(name) \ + ((size_t)len >= sizeof(name)-1 && \ + strncmp(str, (name), sizeof(name)-1) == 0 && \ + ((len == sizeof(name)-1 && !(len = 0)) || \ + (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; \ + } 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); \ + } \ + } while (0) +#define SET_WHEN_UINT(name, vals, num, req) \ + if (NAME_MATCH_VALUE(name)) SET_UINT_LIST(name, vals, num); + + SET_WHEN("gc_stress", *ruby_initial_gc_stress_ptr, Qtrue); + SET_WHEN("core", ruby_enable_coredump, 1); + SET_WHEN("ci", ruby_on_ci, 1); + if (NAME_MATCH_VALUE("rgengc")) { + if (!len) ruby_rgengc_debug = 1; + else SET_UINT_LIST("rgengc", &ruby_rgengc_debug, 1); + return 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; + } +#endif + return 0; +} + +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); + } +} + +#ifdef USE_RUBY_DEBUG_LOG +STATIC_ASSERT(USE_RUBY_DEBUG_LOG, USE_RUBY_DEBUG_LOG ? RUBY_DEVEL : 1); +#endif + +#if RUBY_DEVEL +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 RUBY_DEVEL + +// 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 0x0010 + +enum ruby_debug_log_mode ruby_debug_log_mode; + +static struct { + char *mem; + unsigned int cnt; + char filters[MAX_DEBUG_LOG_FILTER][MAX_DEBUG_LOG_FILTER]; + unsigned int filters_num; + rb_nativethread_lock_t lock; + 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 void +setup_debug_log(void) +{ + // check RUBY_DEBUG_LOG + const char *log_config = getenv("RUBY_DEBUG_LOG"); + if (log_config) { + fprintf(stderr, "RUBY_DEBUG_LOG=%s\n", log_config); + + 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; + if ((debug_log.output = fopen(log_config, "w")) == NULL) { + fprintf(stderr, "can not open %s for RUBY_DEBUG_LOG\n", log_config); + exit(1); + } + setvbuf(debug_log.output, NULL, _IONBF, 0); + } + + rb_nativethread_lock_initialize(&debug_log.lock); + } + + // check RUBY_DEBUG_LOG_FILTER + 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; i++) { + const char *p; + if ((p = strchr(filter_config, ',')) == NULL) { + if (strlen(filter_config) >= MAX_DEBUG_LOG_FILTER) { + fprintf(stderr, "too long: %s (max:%d)\n", filter_config, MAX_DEBUG_LOG_FILTER); + exit(1); + } + strncpy(debug_log.filters[i], filter_config, MAX_DEBUG_LOG_FILTER - 1); + i++; + break; + } + else { + size_t n = p - filter_config; + if (n >= MAX_DEBUG_LOG_FILTER) { + fprintf(stderr, "too long: %s (max:%d)\n", filter_config, MAX_DEBUG_LOG_FILTER); + exit(1); + } + strncpy(debug_log.filters[i], filter_config, n); + filter_config = p+1; + } + } + debug_log.filters_num = i; + for (i=0; i<debug_log.filters_num; i++) { + fprintf(stderr, "RUBY_DEBUG_LOG_FILTER[%d]=%s\n", i, debug_log.filters[i]); + } + } +} + +bool +ruby_debug_log_filter(const char *func_name) +{ + if (debug_log.filters_num > 0) { + for (unsigned int i = 0; i<debug_log.filters_num; i++) { + if (strstr(func_name, debug_log.filters[i]) != NULL) { + return true; + } + } + return false; + } + 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; +} + +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; + + // 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\n", 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\n", r); + len += r; + } + + // Ruby location + int ruby_line; + const char *ruby_file = rb_source_location_cstr(&ruby_line); + 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\n", r); + len += r; + } + + // ractor information + if (ruby_single_main_ractor == NULL) { + rb_ractor_t *cr = GET_RACTOR(); + if (r && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tr:#%u/%u", + (unsigned int)rb_ractor_id(cr), GET_VM()->ractor.cnt); + if (r < 0) rb_bug("ruby_debug_log returns %d\n", r); + len += r; + } + } + + // thread information + if (!rb_thread_alone()) { + const rb_thread_t *th = GET_THREAD(); + if (r && len < MAX_DEBUG_LOG_MESSAGE_LEN) { + r = snprintf(buff + len, MAX_DEBUG_LOG_MESSAGE_LEN - len, "\tth:%p", (void *)th); + if (r < 0) rb_bug("ruby_debug_log returns %d\n", 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); + } +} +#endif // #if RUBY_DEVEL |
