Skip to content

Commit 0cf9aae

Browse files
committed
[Feature #20249] Add crash report configuration
Extend `RUBY_CRASH_REPORT` syntax as comma/space sparated `key=value` pairs. Supported keys: + dump set/reset the following boolean values all + bt if true, show Ruby-level backtrace + vmbt if true, show Ruby-level backtrace with VM information + cbt if true, show C-level backtrace + box if true, show Box information + thread if true, show Thread information + regs if true, show Register information + lf if true, show loaded features + mm if true, show memory maps + additional if true, show additional bug reporters + path output file path to store the report + pipe command and arguments to receive the report Boolean values are: `true`: "t", "y", "1", "true", "yes" `false`: "f", "n", "0", "false", "no"
1 parent c3f6fcc commit 0cf9aae

File tree

9 files changed

+182
-51
lines changed

9 files changed

+182
-51
lines changed

depend

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5107,6 +5107,7 @@ error.$(OBJEXT): $(top_srcdir)/internal/string.h
51075107
error.$(OBJEXT): $(top_srcdir)/internal/struct.h
51085108
error.$(OBJEXT): $(top_srcdir)/internal/symbol.h
51095109
error.$(OBJEXT): $(top_srcdir)/internal/thread.h
5110+
error.$(OBJEXT): $(top_srcdir)/internal/util.h
51105111
error.$(OBJEXT): $(top_srcdir)/internal/variable.h
51115112
error.$(OBJEXT): $(top_srcdir)/internal/vm.h
51125113
error.$(OBJEXT): $(top_srcdir)/internal/warnings.h
@@ -19135,12 +19136,14 @@ vm_dump.$(OBJEXT): $(top_srcdir)/internal/array.h
1913519136
vm_dump.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
1913619137
vm_dump.$(OBJEXT): $(top_srcdir)/internal/box.h
1913719138
vm_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h
19139+
vm_dump.$(OBJEXT): $(top_srcdir)/internal/error.h
1913819140
vm_dump.$(OBJEXT): $(top_srcdir)/internal/gc.h
1913919141
vm_dump.$(OBJEXT): $(top_srcdir)/internal/imemo.h
1914019142
vm_dump.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
1914119143
vm_dump.$(OBJEXT): $(top_srcdir)/internal/serial.h
1914219144
vm_dump.$(OBJEXT): $(top_srcdir)/internal/set_table.h
1914319145
vm_dump.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
19146+
vm_dump.$(OBJEXT): $(top_srcdir)/internal/string.h
1914419147
vm_dump.$(OBJEXT): $(top_srcdir)/internal/struct.h
1914519148
vm_dump.$(OBJEXT): $(top_srcdir)/internal/variable.h
1914619149
vm_dump.$(OBJEXT): $(top_srcdir)/internal/vm.h

error.c

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "internal/symbol.h"
4545
#include "internal/thread.h"
4646
#include "internal/variable.h"
47+
#include "internal/util.h"
4748
#include "ruby/encoding.h"
4849
#include "ruby/st.h"
4950
#include "ruby/util.h"
@@ -866,45 +867,124 @@ expand_report_argument(const char **input_template, struct report_expansion *val
866867

867868
FILE *ruby_popen_writer(char *const *argv, rb_pid_t *pid);
868869

870+
struct crash_report_config {
871+
struct vm_dump_conf dump;
872+
const char *begin;
873+
int len;
874+
bool pipe; // write to pipe
875+
};
876+
877+
static bool
878+
boolconv(const char *word, int len, bool *ret)
879+
{
880+
#define boolword(n, v) \
881+
if (len == rb_strlen_lit(#n) && strncmp(word, #n, len) == 0) {*ret = v; return true;}
882+
boolword(false, false);
883+
boolword(true, true);
884+
boolword(yes, true);
885+
boolword(no, false);
886+
#undef boolword
887+
if (len == 1) {
888+
switch (*word) {
889+
case 't': case 'y': case '1': *ret = true; return true;
890+
case 'f': case 'n': case '0': *ret = false; return true;
891+
}
892+
}
893+
return false;
894+
}
895+
896+
static int
897+
handle_crash_report_word(const char *word, int len, void *arg)
898+
{
899+
struct crash_report_config *cf = arg;
900+
#define isconfname(conf) \
901+
(len > (int)sizeof(#conf) && strncmp(word, #conf "=", sizeof(#conf)) == 0 ? \
902+
(word += sizeof(#conf), len -= sizeof(#conf), 1) : 0)
903+
#define boolconf(conf) \
904+
if (isconfname(conf)) { \
905+
bool v = cf->dump.conf; \
906+
if (boolconv(word, len, &v)) cf->dump.conf = v; \
907+
return 0; \
908+
}
909+
if (isconfname(dump)) {
910+
bool v;
911+
if (boolconv(word, len, &v)) {
912+
cf->dump.bt = v;
913+
cf->dump.vmbt = v;
914+
cf->dump.cbt = v;
915+
cf->dump.box = v;
916+
cf->dump.thread = v;
917+
cf->dump.regs = v;
918+
cf->dump.lf = v;
919+
cf->dump.mm = v;
920+
cf->dump.additional = v;
921+
}
922+
return 0;
923+
}
924+
boolconf(bt);
925+
boolconf(vmbt);
926+
boolconf(cbt);
927+
boolconf(box);
928+
boolconf(thread);
929+
boolconf(regs);
930+
boolconf(lf);
931+
boolconf(mm);
932+
if (!cf->begin) {
933+
if (word[0] == '|' || isconfname(pipe)) {
934+
if (*++word) cf->begin = word;
935+
cf->pipe = true;
936+
return 1;
937+
}
938+
else {
939+
isconfname(path); // skip "path="
940+
cf->begin = word;
941+
cf->len = len;
942+
return 0;
943+
}
944+
}
945+
return 0;
946+
}
947+
869948
static FILE *
870-
open_report_path(const char *template, char *buf, size_t size, rb_pid_t *pid)
949+
open_report_path(struct crash_report_config *conf, char *buf, size_t size, rb_pid_t *pid)
871950
{
872951
struct report_expansion values = {{0}};
873-
874-
if (!template) return NULL;
875-
if (0) fprintf(stderr, "RUBY_CRASH_REPORT=%s\n", buf);
876-
if (*template == '|') {
952+
const char *str = conf->begin;
953+
if (!str) return NULL;
954+
if (conf->pipe) {
877955
char *argv[16], *bufend = buf + size, *p;
878956
int argc;
879-
template++;
880957
for (argc = 0; argc < numberof(argv) - 1; ++argc) {
881-
while (*template && ISSPACE(*template)) template++;
882-
p = expand_report_argument(&template, &values, buf, bufend-buf, true);
958+
while (*str && ISSPACE(*str)) str++;
959+
p = expand_report_argument(&str, &values, buf, bufend-buf, true);
883960
if (!p) break;
884961
argv[argc] = buf;
885962
buf = p;
886963
}
887964
argv[argc] = NULL;
888965
if (!p) return ruby_popen_writer(argv, pid);
889966
}
890-
else if (*template) {
891-
expand_report_argument(&template, &values, buf, size, false);
967+
else if (*str) {
968+
expand_report_argument(&str, &values, buf, size, false);
892969
return fopen(buf, "w");
893970
}
894971
return NULL;
895972
}
896973

897-
static const char *crash_report;
974+
static struct crash_report_config crash_report = {VM_DUMP_CONF_DEFAULT};
898975

899976
/* SIGSEGV handler might have a very small stack. Thus we need to use it carefully. */
900977
#define REPORT_BUG_BUFSIZ 256
901978
static FILE *
902979
bug_report_file(const char *file, int line, rb_pid_t *pid)
903980
{
904981
char buf[REPORT_BUG_BUFSIZ];
905-
const char *report = crash_report;
906-
if (!report) report = getenv("RUBY_CRASH_REPORT");
907-
FILE *out = open_report_path(report, buf, sizeof(buf), pid);
982+
if (!crash_report.begin) {
983+
const char *report = getenv("RUBY_CRASH_REPORT");
984+
if (0) fprintf(stderr, "RUBY_CRASH_REPORT=%s\n", report);
985+
if (report) ruby_set_crash_report(report);
986+
}
987+
FILE *out = open_report_path(&crash_report, buf, sizeof(buf), pid);
908988
int len = err_position_0(buf, sizeof(buf), file, line);
909989

910990
if (out) {
@@ -1011,7 +1091,7 @@ bug_report_begin_valist(FILE *out, const char *fmt, va_list args)
10111091
fputs(buf, out);
10121092
snprintf(buf, sizeof(buf), "\n%s\n\n", rb_dynamic_description);
10131093
fputs(buf, out);
1014-
preface_dump(out);
1094+
if (vm_dump_conf_enabled_any(&crash_report.dump)) preface_dump(out);
10151095
}
10161096

10171097
#define bug_report_begin(out, fmt) do { \
@@ -1025,14 +1105,14 @@ static void
10251105
bug_report_end(FILE *out, rb_pid_t pid)
10261106
{
10271107
/* call additional bug reporters */
1028-
{
1108+
if (crash_report.dump.additional) {
10291109
int i;
10301110
for (i=0; i<bug_reporters_size; i++) {
10311111
struct bug_reporters *reporter = &bug_reporters[i];
10321112
(*reporter->func)(out, reporter->data);
10331113
}
10341114
}
1035-
postscript_dump(out);
1115+
if (vm_dump_conf_enabled_any(&crash_report.dump)) postscript_dump(out);
10361116
finish_report(out, pid);
10371117
}
10381118

@@ -1041,7 +1121,7 @@ bug_report_end(FILE *out, rb_pid_t pid)
10411121
FILE *out = bug_report_file(file, line, &pid); \
10421122
if (out) { \
10431123
bug_report_begin(out, fmt); \
1044-
rb_vm_bugreport(ctx, out); \
1124+
rb_vm_bugreport(&crash_report.dump, ctx, out); \
10451125
bug_report_end(out, pid); \
10461126
} \
10471127
} while (0) \
@@ -1051,25 +1131,15 @@ bug_report_end(FILE *out, rb_pid_t pid)
10511131
FILE *out = bug_report_file(file, line, &pid); \
10521132
if (out) { \
10531133
bug_report_begin_valist(out, fmt, args); \
1054-
rb_vm_bugreport(ctx, out); \
1134+
rb_vm_bugreport(&crash_report.dump, ctx, out); \
10551135
bug_report_end(out, pid); \
10561136
} \
10571137
} while (0) \
10581138

10591139
void
10601140
ruby_set_crash_report(const char *template)
10611141
{
1062-
crash_report = template;
1063-
#if RUBY_DEBUG
1064-
rb_pid_t pid = -1;
1065-
char buf[REPORT_BUG_BUFSIZ];
1066-
FILE *out = open_report_path(template, buf, sizeof(buf), &pid);
1067-
if (out) {
1068-
time_t t = time(NULL);
1069-
fprintf(out, "ruby_test_bug_report: %s", ctime(&t));
1070-
finish_report(out, pid);
1071-
}
1072-
#endif
1142+
ruby_each_words_until(template, handle_crash_report_word, "|", &crash_report);
10731143
}
10741144

10751145
NORETURN(static void die(void));
@@ -1212,9 +1282,10 @@ rb_assert_failure_detail(const char *file, int line, const char *name, const cha
12121282
}
12131283
fprintf(out, "\n%s\n\n", rb_dynamic_description);
12141284

1215-
preface_dump(out);
1216-
rb_vm_bugreport(NULL, out);
1217-
bug_report_end(out, pid);
1285+
bool dump_any = vm_dump_conf_enabled_any(&crash_report.dump);
1286+
if (dump_any) preface_dump(out);
1287+
rb_vm_bugreport(&crash_report.dump, NULL, out);
1288+
if (dump_any) bug_report_end(out, pid);
12181289
}
12191290

12201291
die();

internal/error.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,34 @@ typedef enum {
249249
} ruby_stack_overflow_critical_level;
250250
NORETURN(void rb_ec_stack_overflow(struct rb_execution_context_struct *ec, ruby_stack_overflow_critical_level crit));
251251

252+
struct vm_dump_conf {
253+
unsigned int bt: 1; // Ruby-level backtrace
254+
unsigned int vmbt: 1; // Ruby-level backtrace with VM information
255+
unsigned int cbt: 1; // C-level backtrace
256+
unsigned int box: 1; // Box information
257+
unsigned int thread: 1; // Threading information
258+
unsigned int regs: 1; // Register information
259+
unsigned int lf: 1; // loaded features
260+
unsigned int mm: 1; // memory maps
261+
unsigned int additional: 1; // additional bug reporters
262+
};
263+
264+
#define VM_DUMP_CONF_DEFAULT \
265+
{ \
266+
.bt = 1, .vmbt = 1, .cbt = 1, .box = 1, \
267+
.thread = 1, .regs = 1, .lf = 1, .mm = 1, \
268+
.additional = 1, \
269+
}
270+
271+
static inline bool
272+
vm_dump_conf_enabled_any(const struct vm_dump_conf *dump)
273+
{
274+
return dump->bt || dump->vmbt || dump->cbt || dump->box ||
275+
dump->thread || dump->regs || dump->lf || dump->mm ||
276+
dump->additional;
277+
}
278+
279+
bool rb_vm_bugreport(const struct vm_dump_conf *cf, const void *ctx, FILE *errout);
280+
void ruby_set_crash_report(const char *template);
281+
252282
#endif /* INTERNAL_ERROR_H */

internal/util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
/* util.c */
2020
char *ruby_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve);
2121
char *ruby_hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign, char **rve);
22+
void ruby_each_words_until(const char *str, int (*func)(const char*, int, void*), const char *sep, void *arg);
2223

2324
RUBY_SYMBOL_EXPORT_BEGIN
2425
/* util.c (export) */

test/ruby/test_rubyoptions.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,11 @@ def test_crash_report_pipe_script
973973
end
974974
end
975975

976+
def test_crash_report_no_dump
977+
status, report = assert_crash_report("dump=no,dump.log")
978+
assert_equal("dump.log", report)
979+
end
980+
976981
def test_DATA
977982
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
978983
t.puts "puts DATA.read.inspect"

util.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,22 @@ ruby_each_words(const char *str, void (*func)(const char*, int, void*), void *ar
609609
}
610610
}
611611

612+
void
613+
ruby_each_words_until(const char *str, int (*func)(const char*, int, void*), const char *sep, void *arg)
614+
{
615+
const char *end;
616+
if (!str) return;
617+
for (; *str; str = end) {
618+
while (ISSPACE(*str) || *str == ',') str++;
619+
for (end = str; *end && !ISSPACE(*end); end++) {
620+
if (*end == ',') break;
621+
if (sep && strchr(sep, *end)) break;
622+
}
623+
int ret = (*func)(str, (int)(end - str), arg); /* assume no string exceeds INT_MAX */
624+
if (ret) break;
625+
}
626+
}
627+
612628
#undef strtod
613629
#define strtod ruby_strtod
614630
#undef dtoa

vm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4097,7 +4097,7 @@ extern size_t rb_gc_stack_maxsize;
40974097
static VALUE
40984098
sdr(VALUE self)
40994099
{
4100-
rb_vm_bugreport(NULL, stderr);
4100+
rb_vm_bugreport(NULL, NULL, stderr);
41014101
return Qnil;
41024102
}
41034103

vm_core.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,6 @@ extern bool rb_vmdebug_debug_print_post(const rb_execution_context_t *ec, const
19241924

19251925
#define SDR() rb_vmdebug_stack_dump_raw(GET_EC(), GET_EC()->cfp, stderr)
19261926
#define SDR2(cfp) rb_vmdebug_stack_dump_raw(GET_EC(), (cfp), stderr)
1927-
bool rb_vm_bugreport(const void *, FILE *);
19281927
typedef void (*ruby_sighandler_t)(int);
19291928
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 4, 5)
19301929
NORETURN(void rb_bug_for_fatal_signal(ruby_sighandler_t default_sighandler, int sig, const void *, const char *fmt, ...));

0 commit comments

Comments
 (0)