mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			511 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			511 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**********************************************************************
 | |
| 
 | |
|   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 "id.h"
 | |
| #include "internal/signal.h"
 | |
| #include "internal/util.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_robject_flags     robject_flags;
 | |
|     enum ruby_robject_consts    robject_consts;
 | |
|     enum ruby_rmodule_flags     rmodule_flags;
 | |
|     enum ruby_rstring_flags     rstring_flags;
 | |
|     enum ruby_rstring_consts    rstring_consts;
 | |
|     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
 | 
