* makes heavy use of metadata defined in the V8 binary for inspecting in-memory
* structures. Canned configurations can be manually loaded for V8 binaries
* that predate this metadata. See mdb_v8_cfg.c for details.
+ *
+ * NOTE: This dmod implementation (including this file and related headers and C
+ * files) exist in both the Node and illumos source trees. THESE SHOULD BE KEPT
+ * IN SYNC. The version in the Node tree is built directly into modern Node
+ * binaries as part of the build process, and the version in the illumos source
+ * tree is delivered with the OS for debugging Node binaries that predate
+ * support for including the dmod directly in the binary. Note too that these
+ * files have different licenses to match their corresponding repositories.
*/
/*
static intptr_t V8_StringRepresentationMask;
static intptr_t V8_SeqStringTag;
static intptr_t V8_ConsStringTag;
+static intptr_t V8_SlicedStringTag;
static intptr_t V8_ExternalStringTag;
static intptr_t V8_FailureTag;
static intptr_t V8_FailureTagMask;
static ssize_t V8_OFF_SCRIPT_NAME;
static ssize_t V8_OFF_SEQASCIISTR_CHARS;
static ssize_t V8_OFF_SEQONEBYTESTR_CHARS;
+static ssize_t V8_OFF_SEQTWOBYTESTR_CHARS;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_CODE;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_FUNCTION_TOKEN_POSITION;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_INFERRED_NAME;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_LENGTH;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_SCRIPT;
static ssize_t V8_OFF_SHAREDFUNCTIONINFO_NAME;
+static ssize_t V8_OFF_SLICEDSTRING_PARENT;
+static ssize_t V8_OFF_SLICEDSTRING_OFFSET;
static ssize_t V8_OFF_STRING_LENGTH;
#define NODE_OFF_EXTSTR_DATA 0x4 /* see node_string.h */
{ &V8_StringRepresentationMask, "v8dbg_StringRepresentationMask" },
{ &V8_SeqStringTag, "v8dbg_SeqStringTag" },
{ &V8_ConsStringTag, "v8dbg_ConsStringTag" },
+ { &V8_SlicedStringTag, "v8dbg_SlicedStringTag",
+ V8_CONSTANT_FALLBACK(0, 0), 0x3 },
{ &V8_ExternalStringTag, "v8dbg_ExternalStringTag" },
{ &V8_FailureTag, "v8dbg_FailureTag" },
{ &V8_FailureTagMask, "v8dbg_FailureTagMask" },
const char *v8o_class;
const char *v8o_member;
boolean_t v8o_optional;
+ intptr_t v8o_fallback;
} v8_offset_t;
static v8_offset_t v8_offsets[] = {
"SeqAsciiString", "chars", B_TRUE },
{ &V8_OFF_SEQONEBYTESTR_CHARS,
"SeqOneByteString", "chars", B_TRUE },
+ { &V8_OFF_SEQTWOBYTESTR_CHARS,
+ "SeqTwoByteString", "chars", B_TRUE },
{ &V8_OFF_SHAREDFUNCTIONINFO_CODE,
"SharedFunctionInfo", "code" },
{ &V8_OFF_SHAREDFUNCTIONINFO_FUNCTION_TOKEN_POSITION,
"SharedFunctionInfo", "name" },
{ &V8_OFF_SHAREDFUNCTIONINFO_SCRIPT,
"SharedFunctionInfo", "script" },
+ { &V8_OFF_SLICEDSTRING_OFFSET,
+ "SlicedString", "offset" },
+ { &V8_OFF_SLICEDSTRING_PARENT,
+ "SlicedString", "parent", B_TRUE },
{ &V8_OFF_STRING_LENGTH,
"String", "length" },
};
if (V8_OFF_SEQONEBYTESTR_CHARS != -1)
V8_OFF_SEQASCIISTR_CHARS = V8_OFF_SEQONEBYTESTR_CHARS;
+ if (V8_OFF_SEQTWOBYTESTR_CHARS == -1)
+ V8_OFF_SEQTWOBYTESTR_CHARS = V8_OFF_SEQASCIISTR_CHARS;
+
+ if (V8_OFF_SLICEDSTRING_PARENT == -1)
+ V8_OFF_SLICEDSTRING_PARENT = V8_OFF_SLICEDSTRING_OFFSET -
+ sizeof (uintptr_t);
+
return (failed ? -1 : 0);
}
#define JSSTR_NUDE JSSTR_NONE
#define JSSTR_VERBOSE 0x1
#define JSSTR_QUOTED 0x2
+#define JSSTR_ISASCII 0x4
static int jsstr_print(uintptr_t, uint_t, char **, size_t *);
static boolean_t jsobj_is_undefined(uintptr_t addr);
if (!(flags & UM_GC))
mdb_free(*retp, len * sizeof (uintptr_t));
+ *retp = NULL;
return (-1);
}
}
/*
- * Print the ASCII string for the given ASCII JS string, expanding ConsStrings
- * and ExternalStrings as needed.
+ * Print the ASCII string for the given JS string, expanding ConsStrings and
+ * ExternalStrings as needed.
*/
-static int jsstr_print_seq(uintptr_t, uint_t, char **, size_t *);
+static int jsstr_print_seq(uintptr_t, uint_t, char **, size_t *, size_t,
+ ssize_t);
static int jsstr_print_cons(uintptr_t, uint_t, char **, size_t *);
+static int jsstr_print_sliced(uintptr_t, uint_t, char **, size_t *);
static int jsstr_print_external(uintptr_t, uint_t, char **, size_t *);
static int
return (0);
}
- if (!V8_STRENC_ASCII(typebyte)) {
- (void) bsnprintf(bufp, lenp, "<two-byte string>");
- return (0);
- }
-
if (verbose) {
lbufp = buf;
llen = sizeof (buf);
(void) mdb_inc_indent(4);
}
+ if (V8_STRENC_ASCII(typebyte))
+ flags |= JSSTR_ISASCII;
+ else
+ flags &= ~JSSTR_ISASCII;
+
if (V8_STRREP_SEQ(typebyte))
- err = jsstr_print_seq(addr, flags, bufp, lenp);
+ err = jsstr_print_seq(addr, flags, bufp, lenp, 0, -1);
else if (V8_STRREP_CONS(typebyte))
err = jsstr_print_cons(addr, flags, bufp, lenp);
else if (V8_STRREP_EXT(typebyte))
err = jsstr_print_external(addr, flags, bufp, lenp);
+ else if (V8_STRREP_SLICED(typebyte))
+ err = jsstr_print_sliced(addr, flags, bufp, lenp);
else {
(void) bsnprintf(bufp, lenp, "<unknown string type>");
err = -1;
}
static int
-jsstr_print_seq(uintptr_t addr, uint_t flags, char **bufp, size_t *lenp)
+jsstr_print_seq(uintptr_t addr, uint_t flags, char **bufp, size_t *lenp,
+ size_t sliceoffset, ssize_t slicelen)
{
/*
* To allow the caller to allocate a very large buffer for strings,
* we'll allocate a buffer sized based on our input, making it at
* least enough space for our ellipsis and at most 256K.
*/
- uintptr_t len, rlen, blen = *lenp + sizeof ("[...]") + 1;
- char *buf = alloca(MIN(blen, 256 * 1024));
+ uintptr_t i, nstrchrs, nreadbytes, nreadoffset, blen, nstrbytes;
boolean_t verbose = flags & JSSTR_VERBOSE ? B_TRUE : B_FALSE;
boolean_t quoted = flags & JSSTR_QUOTED ? B_TRUE : B_FALSE;
+ char *buf;
+ uint16_t chrval;
+
+ blen = MIN(*lenp, 256 * 1024);
+ buf = alloca(blen);
- if (read_heap_smi(&len, addr, V8_OFF_STRING_LENGTH) != 0)
+ if (read_heap_smi(&nstrchrs, addr, V8_OFF_STRING_LENGTH) != 0)
return (-1);
- rlen = len <= blen - 1 ? len : blen - sizeof ("[...]");
+ if (slicelen != -1)
+ nstrchrs = slicelen;
+ if (nstrchrs < 0)
+ nstrchrs = 0;
+
+ if ((flags & JSSTR_ISASCII) != 0) {
+ nstrbytes = nstrchrs;
+ nreadoffset = sliceoffset;
+ } else {
+ nstrbytes = 2 * nstrchrs;
+ nreadoffset = 2 * sliceoffset;
+ }
+
+ nreadbytes = nstrbytes + sizeof ("\"\"") <= blen ? nstrbytes :
+ blen - sizeof ("\"\"[...]");
if (verbose)
- mdb_printf("length: %d, will read: %d\n", len, rlen);
+ mdb_printf("length: %d chars (%d bytes), "
+ "will read %d bytes from offset %d\n",
+ nstrchrs, nstrbytes, nreadbytes, nreadoffset);
+
+ if (nstrbytes == 0) {
+ (void) bsnprintf(bufp, lenp, "%s%s",
+ quoted ? "\"" : "", quoted ? "\"" : "");
+ return (0);
+ }
buf[0] = '\0';
- if (rlen > 0 && mdb_readstr(buf, rlen + 1,
- addr + V8_OFF_SEQASCIISTR_CHARS) == -1) {
- v8_warn("failed to read SeqString data");
- return (-1);
- }
+ if ((flags & JSSTR_ISASCII) != 0) {
+ if (mdb_readstr(buf, nreadbytes + 1,
+ addr + V8_OFF_SEQASCIISTR_CHARS + nreadoffset) == -1) {
+ v8_warn("failed to read SeqString data");
+ return (-1);
+ }
- if (rlen != len)
- (void) strlcat(buf, "[...]", blen);
+ if (nreadbytes != nstrbytes)
+ (void) strlcat(buf, "[...]", blen);
- if (verbose)
- mdb_printf("value: \"%s\"\n", buf);
+ (void) bsnprintf(bufp, lenp, "%s%s%s",
+ quoted ? "\"" : "", buf, quoted ? "\"" : "");
+ } else {
+ if (mdb_readstr(buf, nreadbytes,
+ addr + V8_OFF_SEQTWOBYTESTR_CHARS + nreadoffset) == -1) {
+ v8_warn("failed to read SeqTwoByteString data");
+ return (-1);
+ }
- (void) bsnprintf(bufp, lenp, "%s%s%s",
- quoted ? "\"" : "", buf, quoted ? "\"" : "");
+ (void) bsnprintf(bufp, lenp, "%s", quoted ? "\"" : "");
+ for (i = 0; i < nreadbytes; i += 2) {
+ chrval = *((uint16_t *)(buf + i));
+ (void) bsnprintf(bufp, lenp, "%c",
+ (isascii(chrval) || chrval == 0) ?
+ (char)chrval : '?');
+ }
+ if (nreadbytes != nstrbytes)
+ (void) bsnprintf(bufp, lenp, "[...]");
+ (void) bsnprintf(bufp, lenp, "%s", quoted ? "\"" : "");
+ }
return (0);
}
}
static int
-jsstr_print_external(uintptr_t addr, uint_t flags, char **bufp,
- size_t *lenp)
+jsstr_print_sliced(uintptr_t addr, uint_t flags, char **bufp, size_t *lenp)
+{
+ uintptr_t parent, offset, length;
+ uint8_t typebyte;
+ boolean_t verbose = flags & JSSTR_VERBOSE ? B_TRUE : B_FALSE;
+ boolean_t quoted = flags & JSSTR_QUOTED ? B_TRUE : B_FALSE;
+ uint_t newflags;
+
+ if (read_heap_ptr(&parent, addr, V8_OFF_SLICEDSTRING_PARENT) != 0 ||
+ read_heap_smi(&offset, addr, V8_OFF_SLICEDSTRING_OFFSET) != 0 ||
+ read_heap_smi(&length, addr, V8_OFF_STRING_LENGTH) != 0)
+ return (-1);
+
+ if (verbose)
+ mdb_printf("parent: %p, offset = %d, length = %d\n",
+ parent, offset, length);
+
+ if (read_typebyte(&typebyte, parent) != 0) {
+ v8_warn("SlicedString %s: failed to read parent's type", addr);
+ (void) bsnprintf(bufp, lenp, "<sliced string>");
+ return (0);
+ }
+
+ if (!V8_STRREP_SEQ(typebyte)) {
+ v8_warn("SlicedString %s: parent is not a sequential string",
+ addr);
+ (void) bsnprintf(bufp, lenp, "<sliced string>");
+ return (0);
+ }
+
+ if (quoted)
+ (void) bsnprintf(bufp, lenp, "\"");
+
+ newflags = verbose ? JSSTR_VERBOSE : 0;
+ if (V8_STRENC_ASCII(typebyte))
+ newflags |= JSSTR_ISASCII;
+ if (jsstr_print_seq(parent, newflags, bufp, lenp, offset, length) != 0)
+ return (-1);
+
+ if (quoted)
+ (void) bsnprintf(bufp, lenp, "\"");
+
+ return (0);
+}
+
+static int
+jsstr_print_external(uintptr_t addr, uint_t flags, char **bufp, size_t *lenp)
{
uintptr_t ptr1, ptr2;
size_t blen = *lenp + 1;
char *buf = alloca(blen);
boolean_t quoted = flags & JSSTR_QUOTED ? B_TRUE : B_FALSE;
+ if ((flags & JSSTR_ISASCII) == 0) {
+ (void) bsnprintf(bufp, lenp, "<external two-byte string>");
+ return (0);
+ }
+
if (flags & JSSTR_VERBOSE)
mdb_printf("assuming Node.js string\n");
#define V8_MAP_PTR(ptr) \
((ptr & ~V8_HeapObjectTagMask) | V8_HeapObjectTag)
+#define V8_TYPE_SCRIPT(type) \
+ ((type) == V8_IT_SCRIPT)
+
/*
* Determine the encoding and representation of a V8 string.
*/
#define ASCII_SEQSTR(value) \
(V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_SEQ(value))
-#define ASCII_CONSSTR(value) \
- (V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_CONS(value))
+#define TWOBYTE_SEQSTR(value) \
+ (V8_TYPE_STRING(value) && !V8_STRENC_ASCII(value) && V8_STRREP_SEQ(value))
+
+#define IS_CONSSTR(value) \
+ (V8_TYPE_STRING(value) && V8_STRREP_CONS(value))
#define ASCII_EXTSTR(value) \
(V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_EXT(value))
* "len": the string length
*
* "attrs": the type identifier for the string, which indicates the
- * encoding and representation. We're only interested in ASCII
- * encoded strings whose representation is one of:
+ * encoding and representation. We're only interested in strings
+ * whose representation is one of:
*
- * SeqString stored directly as a char array inside the object
+ * SeqOneByteString stored directly as a char array inside the object
*
- * ConsString pointer to two strings that should be concatenated
+ * SeqTwoByteString stored as a UTF-16 char array inside the object
*
- * ExternalString pointer to a char* outside the V8 heap
+ * ConsString pointer to two strings that should be concatenated
+ *
+ * ExternalString pointer to a char* outside the V8 heap
*/
/*
this->map = V8_MAP_PTR(COPYIN_PTR(str + V8_OFF_HEAPOBJ_MAP)); \
attrs = COPYIN_UINT8(this->map + V8_OFF_MAP_ATTRS);
+#define APPEND_SEQSTR(str, len, attrs) \
+ APPEND_SEQONEBYTESTR(str, len, attrs) \
+ APPEND_SEQTWOBYTESTR(str, len, attrs)
+
/*
- * Print out the given SeqString, or do nothing if the string is not an ASCII
- * SeqString.
+ * Print out the given SeqOneByteString, or do nothing if the string is not an ASCII
+ * SeqOneByteString.
*/
-#define APPEND_SEQSTR(str, len, attrs) \
- dtrace:helper:ustack: \
- /!this->done && len > 0 && ASCII_SEQSTR(attrs)/ \
- { \
+#define APPEND_SEQONEBYTESTR(str, len, attrs) \
+ dtrace:helper:ustack: \
+ /!this->done && len > 0 && ASCII_SEQSTR(attrs)/ \
+ { \
copyinto(str + V8_OFF_STR_CHARS, len, this->buf + this->off); \
this->off += len; \
}
/*
+ * LOOP_ITER: macro to paste "block" while "ivar" is less than "dynmax" and
+ * "statmax". The subsequent LOOP_{4,8} macros facilitate pasting the same
+ * thing 4 and 8 times, respectively. Like much of the rest of the code in this
+ * file, this is regrettably necessary given the constraints under which we're
+ * expected to run.
+ */
+#define LOOP_ITER(ivar, dynmax, statmax, block) \
+ ((ivar) < (dynmax)) && ((ivar) < (statmax)) && (block); (ivar)++;
+
+#define LOOP_4(block) \
+ block \
+ block \
+ block \
+ block \
+
+#define LOOP_8(block) \
+ LOOP_4(block) \
+ LOOP_4(block)
+
+/*
+ * Print out the given SeqTwoByteString, or do nothing if the string is not an ASCII
+ * SeqTwoByteString. NOTE: if you bump MAX_TWOBYTESTR_CHARS, you'll also need
+ * to modify the LOOP_* macro calls below to match.
+ */
+#define MAX_TWOBYTESTR_CHARS 128
+#define MAX_TWOBYTESTR_BYTES (2 * MAX_TWOBYTESTR_CHARS)
+#define TO_ASCII(c) ((c) < 128 ? (c) : '?')
+
+#define APPEND_SEQTWOBYTESTR(str, len, attrs) \
+ dtrace:helper:ustack: \
+ /!this->done && len > 0 && TWOBYTE_SEQSTR(attrs)/ \
+ { \
+ this->i = 0; \
+ this->stbuf = (uint16_t *)alloca(MAX_TWOBYTESTR_BYTES + 2); \
+ copyinto(str + V8_OFF_TWOBYTESTR_CHARS, \
+ MAX_TWOBYTESTR_BYTES, this->stbuf); \
+ this->stbuf[MAX_TWOBYTESTR_BYTES - 1] = '\0'; \
+ this->stbuf[MAX_TWOBYTESTR_BYTES] = '\0'; \
+ \
+ LOOP_8(LOOP_8(LOOP_4(LOOP_ITER(this->i, len, \
+ MAX_TWOBYTESTR_CHARS, \
+ APPEND_CHR(TO_ASCII(this->stbuf[this->i])))))) \
+ \
+ this->i = 0; \
+ this->stbuf = 0; \
+ }
+
+/*
* Print out the given Node.js ExternalString, or do nothing if the string is
* not an ASCII ExternalString.
*/
#define APPEND_NODESTR(str, len, attrs) \
- dtrace:helper:ustack: \
- /!this->done && len > 0 && ASCII_EXTSTR(attrs)/ \
- { \
- this->resource = COPYIN_PTR(str + V8_OFF_EXTSTR_RSRC); \
+ dtrace:helper:ustack: \
+ /!this->done && len > 0 && ASCII_EXTSTR(attrs)/ \
+ { \
+ this->resource = COPYIN_PTR(str + V8_OFF_EXTSTR_RSRC); \
this->dataptr = COPYIN_PTR(this->resource + NODE_OFF_EXTSTR_DATA); \
copyinto(this->dataptr, len, this->buf + this->off); \
this->off += len; \
*/
#define EXPAND_STR(str, len, attrs, s1s, s1l, s1a, s2s, s2l, s2a) \
dtrace:helper:ustack: \
- /!this->done && len > 0 && ASCII_CONSSTR(attrs)/ \
+ /!this->done && len > 0 && IS_CONSSTR(attrs)/ \
{ \
len = 0; \
\
this->funcnamelen = 0;
this->funcnameattrs = 0;
this->script = (off_t) 0;
+ this->scriptattrs = 0;
this->scriptnamestr = (off_t) 0;
this->scriptnamelen = 0;
this->scriptnameattrs = 0;
stringof(this->buf);
}
+dtrace:helper:ustack:
+/!this->done && IS_SMI(this->marker) &&
+ SMI_VALUE(this->marker) == V8_FT_STUB/
+{
+ this->done = 1;
+ APPEND_CHR8('<','<',' ','s','t','u','b',' ');
+ APPEND_CHR4('>','>','\0','\0');
+ stringof(this->buf);
+}
+
/*
* Now check for internal frames that we can only identify by seeing that
* there's a Code object where there would be a JSFunction object for a
APPEND_V8STR(this->funcnamestr, this->funcnamelen, this->funcnameattrs)
/*
- * Now look for the name of the script where the function was defined.
+ * Now look for the name of the script where the function was defined. The
+ * "script" itself may be undefined for special functions like "RegExp".
*/
dtrace:helper:ustack:
/!this->done/
{
this->script = COPYIN_PTR(this->shared + V8_OFF_SHARED_SCRIPT);
+ this->map = V8_MAP_PTR(COPYIN_PTR(this->script + V8_OFF_HEAPOBJ_MAP));
+ this->scriptattrs = COPYIN_UINT8(this->map + V8_OFF_MAP_ATTRS);
+}
+
+dtrace:helper:ustack:
+/!this->done && !V8_TYPE_SCRIPT(this->scriptattrs)/
+{
+ APPEND_CHR('\0');
+ this->done = 1;
+ stringof(this->buf);
+}
+
+
+dtrace:helper:ustack:
+/!this->done/
+{
this->scriptnamestr = COPYIN_PTR(this->script + V8_OFF_SCRIPT_NAME);
LOAD_STRFIELDS(this->scriptnamestr, this->scriptnamelen,
this->scriptnameattrs);
-
- APPEND_CHR4(' ','a','t',' ');
}
dtrace:helper:ustack:
-/!this->done && this->scriptnamelen == 0/
+/!this->done && this->scriptnamelen != 0/
{
- APPEND_CHR8('<','u','n','k','n','o','w','n');
- APPEND_CHR('>');
+ APPEND_CHR4(' ','a','t',' ');
}
APPEND_V8STR(this->scriptnamestr, this->scriptnamelen, this->scriptnameattrs)
}
dtrace:helper:ustack:
+/!this->done && this->le_attrs != V8_IT_FIXEDARRAY && this->position == 0/
+{
+ APPEND_CHR('\0');
+ this->done = 1;
+ stringof(this->buf);
+}
+
+dtrace:helper:ustack:
/!this->done && this->le_attrs != V8_IT_FIXEDARRAY/
{
/*
* undefined because V8 has not had to compute it yet. In this case we
* just show the raw position and call it a day.
*/
- APPEND_CHR8(' ','p','o','s','i','t','i','o');
- APPEND_CHR('n');
+ APPEND_CHR4(' ','p','o','s');
APPEND_CHR(' ');
- APPEND_NUM(this->position);
+ APPEND_NUM(SMI_VALUE(this->position));
APPEND_CHR('\0');
this->done = 1;
stringof(this->buf);