mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
7424ea184f
This commit implements Objects on Variable Width Allocation. This allows Objects with more ivars to be embedded (i.e. contents directly follow the object header) which improves performance through better cache locality.
631 lines
17 KiB
C
631 lines
17 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 "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;
|
|
#if !USE_RVARGC
|
|
enum ruby_robject_consts robject_consts;
|
|
#endif
|
|
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);
|
|
}
|
|
}
|
|
|
|
#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",
|
|
};
|
|
|
|
static struct {
|
|
char *mem;
|
|
unsigned int cnt;
|
|
struct debug_log_filter filters[MAX_DEBUG_LOG_FILTER_NUM];
|
|
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 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;
|
|
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);
|
|
}
|
|
|
|
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]" : "");
|
|
rb_nativethread_lock_initialize(&debug_log.lock);
|
|
|
|
setup_debug_log_filter();
|
|
}
|
|
}
|
|
|
|
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
|
|
// retunrs 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (rb_current_execution_context(false)) {
|
|
// 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
|
|
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:%u", rb_th_serial(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 USE_RUBY_DEBUG_LOG
|