Skip to content

Commit

Permalink
#32: fix ::findjsobjects for V8 4.5.x
Browse files Browse the repository at this point in the history
This change is a work in progress to fix ::findjsobjects for core dumps
generated by Node.js 4.0.x (and possibly later).

The current status of this change fixes how mdb_v8 gets retrieves the
constructor of a JavaScript object given its Map.

It also fixes the problem of accessing objects' properties that was
caused by a typo in "v8db_propindex_mask" that should have been
"v8db_prop_index_mask" (note the underscore in "prop_index") in my
previous changes.

It has a "fallbacks" members for v8_offets so that we can have a
fallback for typed arrays' length's offset.

Finally, this change allows ::findjsobjects to find Buffer instances and
inspect them.

However, due to how Buffer instances are implemented in node v4.x and
later, they are currently seen by mdb_v8 as having a constructor named
"Uint8Array" instead of "Buffer". Even though Buffer instances are
actually now Uint8Array instances, their prototype is set to
Buffer.prototype, and someBufferInstance.constructor.name returns
'Buffer', so mdb_v8 should be able to get 'Buffer' as the constructor
name.

It fixes #32 in its current state, and it represents some
progress but I still want to investigate why we can't get the proper
constructor name for Buffer instances.
  • Loading branch information
misterdjules authored and Julien Gilli committed Sep 18, 2015
1 parent 0408e3b commit c5496b6
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 16 deletions.
174 changes: 161 additions & 13 deletions src/mdb_v8.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ intptr_t V8_TYPE_JSREGEXP = -1;
intptr_t V8_TYPE_HEAPNUMBER = -1;
intptr_t V8_TYPE_ODDBALL = -1;
intptr_t V8_TYPE_FIXEDARRAY = -1;
intptr_t V8_TYPE_MAP = -1;
intptr_t V8_TYPE_JSTYPEDARRAY = -1;

static intptr_t V8_ELEMENTS_KIND_SHIFT;
static intptr_t V8_ELEMENTS_KIND_BITCOUNT;
Expand Down Expand Up @@ -220,6 +222,7 @@ ssize_t V8_OFF_SHAREDFUNCTIONINFO_NAME;
ssize_t V8_OFF_SLICEDSTRING_PARENT;
ssize_t V8_OFF_SLICEDSTRING_OFFSET;
ssize_t V8_OFF_STRING_LENGTH;
ssize_t V8_OFF_JSTYPEDARRAY_LENGTH;

/* see node_string.h */
#define NODE_OFF_EXTSTR_DATA sizeof (uintptr_t)
Expand Down Expand Up @@ -308,9 +311,9 @@ static v8_constant_t v8_constants[] = {
V8_CONSTANT_FALLBACK(3, 11), 3 },
{ &V8_DICT_START_INDEX, "v8dbg_dict_start_index",
V8_CONSTANT_FALLBACK(3, 11), 3 },
{ &V8_PROPINDEX_MASK, "v8dbg_propindex_mask",
{ &V8_PROPINDEX_MASK, "v8dbg_prop_index_mask",
V8_CONSTANT_FALLBACK(3, 26), 0x3ff00000 },
{ &V8_PROPINDEX_SHIFT, "v8dbg_propindex_shift",
{ &V8_PROPINDEX_SHIFT, "v8dbg_prop_index_shift",
V8_CONSTANT_FALLBACK(3, 26), 20 },
{ &V8_ISSHARED_SHIFT, "v8dbg_isshared_shift",
V8_CONSTANT_FALLBACK(3, 11), 0 },
Expand Down Expand Up @@ -377,6 +380,7 @@ typedef struct v8_offset {
const char *v8o_member;
boolean_t v8o_optional;
uint32_t v8o_flags;
intptr_t v8o_fallback;
} v8_offset_t;

static v8_offset_t v8_offsets[] = {
Expand Down Expand Up @@ -468,6 +472,9 @@ static v8_offset_t v8_offsets[] = {
"SlicedString", "parent", B_TRUE },
{ &V8_OFF_STRING_LENGTH,
"String", "length" },
{ &V8_OFF_JSTYPEDARRAY_LENGTH,
"JSTypedArray", "length",
B_FALSE, V8_CONSTANT_FALLBACK(4, 5), 55 },
};

static int v8_noffsets = sizeof (v8_offsets) / sizeof (v8_offsets[0]);
Expand Down Expand Up @@ -541,6 +548,7 @@ static int jsobj_print_number(uintptr_t, jsobj_print_t *);
static int jsobj_print_oddball(uintptr_t, jsobj_print_t *);
static int jsobj_print_jsobject(uintptr_t, jsobj_print_t *);
static int jsobj_print_jsarray(uintptr_t, jsobj_print_t *);
static int jsobj_print_jstyped_array(uintptr_t, jsobj_print_t *);
static int jsobj_print_jsfunction(uintptr_t, jsobj_print_t *);
static int jsobj_print_jsdate(uintptr_t, jsobj_print_t *);
static int jsobj_print_jsregexp(uintptr_t, jsobj_print_t *);
Expand Down Expand Up @@ -583,6 +591,7 @@ autoconfigure(v8_cfg_t *cfgp)
int failed = 0;
int constant_optional, constant_removed, constant_added;
int offset_optional, offset_removed, offset_added;
int offset_fallback;
int v8_older, v8_at_least;

assert(v8_classes == NULL);
Expand Down Expand Up @@ -684,6 +693,12 @@ autoconfigure(v8_cfg_t *cfgp)

if (strcmp(ep->v8e_name, "Oddball") == 0)
V8_TYPE_ODDBALL = ep->v8e_value;

if (strcmp(ep->v8e_name, "Map") == 0)
V8_TYPE_MAP = ep->v8e_value;

if (strcmp(ep->v8e_name, "JSTypedArray") == 0)
V8_TYPE_JSTYPEDARRAY = ep->v8e_value;
}

if (V8_TYPE_JSOBJECT == -1) {
Expand All @@ -706,6 +721,11 @@ autoconfigure(v8_cfg_t *cfgp)
failed++;
}

if (V8_TYPE_JSTYPEDARRAY == -1) {
mdb_warn("couldn't find JSTypedArray type\n");
failed++;
}

/*
* It's non-fatal if we can't find HeapNumber, JSDate, JSRegExp, or
* Oddball because they're only used for heuristics. It's not even a
Expand Down Expand Up @@ -755,7 +775,8 @@ autoconfigure(v8_cfg_t *cfgp)
offset_optional = offp->v8o_flags & V8_CONSTANT_OPTIONAL;
offset_removed = offp->v8o_flags & V8_CONSTANT_REMOVED;
offset_added = offp->v8o_flags & V8_CONSTANT_ADDED;
v8_older = v8_version_older(v8_major, v8_minor, cnp->v8c_flags);
v8_older = v8_version_older(v8_major,
v8_minor, offp->v8o_flags);
v8_at_least = v8_version_at_least(v8_major,
v8_minor, offp->v8o_flags);

Expand All @@ -766,6 +787,21 @@ autoconfigure(v8_cfg_t *cfgp)
offp->v8o_class, offp->v8o_member);
failed++;
}

offset_fallback = offp->v8o_flags & V8_CONSTANT_HASFALLBACK;
if (!offset_fallback ||
v8_major < V8_CONSTANT_MAJOR(offp->v8o_flags) ||
(v8_major == V8_CONSTANT_MAJOR(offp->v8o_flags) &&
v8_minor < V8_CONSTANT_MINOR(offp->v8o_flags))) {
*offp->v8o_valp = -1;
continue;
}

/*
* We have a fallback -- and we know that the version satisfies
* the fallback's version constraints; use the fallback value.
*/
*offp->v8o_valp = offp->v8o_fallback;
}

if (!((V8_OFF_SEQASCIISTR_CHARS != -1) ^
Expand Down Expand Up @@ -1515,6 +1551,46 @@ read_heap_dict(uintptr_t addr,
return (rval);
}

/*
* Given a JavaScript object's Map "map", stores its constructor
* in valp. Returns 0 if it succeeded, -1 otherwise.
*/
static int
get_map_constructor(uintptr_t *valp, uintptr_t map) {
uintptr_t constructor_candidate;
int constructor_found = 0;
uint8_t type;

/*
* https://codereview.chromium.org/950283002, which landed in V8 4.3.x,
* makes the "constructor" and "backpointer to transitions" field
* overlap. In order to retrieve the constructor from a Map, we follow
* the chain of "constructor_or_backpointer" pointers until we find an
* object that is _not_ a Map. This is the same algorithm as
* Map::GetConstructor in src/objects-inl.h.
*/
while (constructor_found == 0) {
if (read_heap_ptr(&constructor_candidate,
map, V8_OFF_MAP_CONSTRUCTOR) != 0)
return (-1);

if (read_typebyte(&type, constructor_candidate) != 0)
return (-1);

if (type != V8_TYPE_MAP) {
constructor_found = 1;
*valp = constructor_candidate;
}

map = constructor_candidate;
}

if (constructor_found == 1)
return (0);

return (-1);
}

/*
* Given an object, returns in "buf" the name of the constructor function. With
* "verbose", prints the pointer to the JSFunction object. Given anything else,
Expand All @@ -1529,14 +1605,15 @@ obj_jsconstructor(uintptr_t addr, char **bufp, size_t *lenp, boolean_t verbose)

if (!V8_IS_HEAPOBJECT(addr) ||
read_typebyte(&type, addr) != 0 ||
(type != V8_TYPE_JSOBJECT && type != V8_TYPE_JSARRAY)) {
(type != V8_TYPE_JSOBJECT &&
type != V8_TYPE_JSARRAY &&
type != V8_TYPE_JSTYPEDARRAY)) {
mdb_warn("%p is not a JSObject\n", addr);
return (-1);
}

if (mdb_vread(&map, sizeof (map), addr + V8_OFF_HEAPOBJECT_MAP) == -1 ||
mdb_vread(&consfunc, sizeof (consfunc),
map + V8_OFF_MAP_CONSTRUCTOR) == -1) {
get_map_constructor(&consfunc, map) == -1) {
mdb_warn("unable to read object map\n");
return (-1);
}
Expand Down Expand Up @@ -1616,8 +1693,7 @@ obj_jstype(uintptr_t addr, char **bufp, size_t *lenp, uint8_t *typep)

if (strcmp(typename, "JSObject") == 0 &&
mdb_vread(&map, sizeof (map), addr + V8_OFF_HEAPOBJECT_MAP) != -1 &&
mdb_vread(&consfunc, sizeof (consfunc),
map + V8_OFF_MAP_CONSTRUCTOR) != -1 &&
get_map_constructor(&consfunc, map) != -1 &&
read_typebyte(&typebyte, consfunc) == 0 &&
strcmp(enum_lookup_str(v8_types, typebyte, ""),
"JSFunction") == 0 &&
Expand Down Expand Up @@ -2196,7 +2272,8 @@ jsobj_maybe_garbage(uintptr_t addr)
type != V8_TYPE_JSARRAY &&
type != V8_TYPE_JSFUNCTION &&
type != V8_TYPE_JSDATE &&
type != V8_TYPE_JSREGEXP)));
type != V8_TYPE_JSREGEXP &&
type != V8_TYPE_JSTYPEDARRAY)));
}

/*
Expand Down Expand Up @@ -2362,8 +2439,12 @@ jsobj_properties(uintptr_t addr,
* so, it contains numerically-named properties. Whether or not there
* are any numerically-named properties, there may be other kinds of
* properties.
* Do not consider instances of JSTypedArray, as they use the elements
* member to store their external data, not numerically-named
* properties.
*/
if (V8_ELEMENTS_KIND_SHIFT != -1 &&
type != V8_TYPE_JSTYPEDARRAY &&
read_heap_ptr(&elements, addr, V8_OFF_JSOBJECT_ELEMENTS) == 0 &&
read_heap_array(elements, &elts, &len, UM_SLEEP) == 0 && len != 0) {
uint8_t bit_field2, kind;
Expand Down Expand Up @@ -3020,6 +3101,7 @@ jsobj_print(uintptr_t addr, jsobj_print_t *jsop)
{ "Oddball", jsobj_print_oddball },
{ "JSObject", jsobj_print_jsobject },
{ "JSArray", jsobj_print_jsarray },
{ "JSTypedArray", jsobj_print_jstyped_array },
{ "JSFunction", jsobj_print_jsfunction },
{ "JSDate", jsobj_print_jsdate },
{ "JSRegExp", jsobj_print_jsregexp },
Expand Down Expand Up @@ -3361,6 +3443,60 @@ jsobj_print_jsarray(uintptr_t addr, jsobj_print_t *jsop)
return (0);
}

static int
jsobj_print_jstyped_array(uintptr_t addr, jsobj_print_t *jsop)
{
char **bufp = jsop->jsop_bufp;
size_t *lenp = jsop->jsop_lenp;
int indent = jsop->jsop_indent;
uintptr_t length;

if (V8_OFF_JSTYPEDARRAY_LENGTH == -1 ||
read_heap_smi(&length, addr, V8_OFF_JSTYPEDARRAY_LENGTH) != 0) {
(void) bsnprintf(bufp, lenp,
"<array (failed to read jstypedarray length)>");
return (-1);
}

if (jsop->jsop_member != NULL) {
if (strcmp(jsop->jsop_member, "length") == 0) {

if (jsop->jsop_baseaddr != NULL)
(void) bsnprintf(bufp, lenp, "%p: ",
jsop->jsop_baseaddr);

(void) bsnprintf(bufp, lenp, "%d", (int)length);
jsop->jsop_found = B_TRUE;
jsop->jsop_member = NULL;
return (0);
}

return (jsobj_properties(addr, jsobj_print_prop_member,
jsop, &jsop->jsop_propinfo));
}

(void) bsnprintf(bufp, lenp, "{\n");
(void) bsnprintf(bufp, lenp, "%*s", indent + 4, "");

(void) bsnprintf(bufp, lenp, "\"length\": ");

if (jsop->jsop_baseaddr != NULL && jsop->jsop_member == NULL)
(void) bsnprintf(bufp, lenp, "%p: ", jsop->jsop_baseaddr);

(void) bsnprintf(bufp, lenp, "%d,", (int)length);

++jsop->jsop_nprops;

jsobj_properties(addr, jsobj_print_prop,
jsop, &jsop->jsop_propinfo);
(void) bsnprintf(bufp, lenp, "\n");

(void) bsnprintf(bufp, lenp, "%*s", indent, "");
(void) bsnprintf(bufp, lenp, "}");

return (0);
}

static int
jsobj_print_jsfunction(uintptr_t addr, jsobj_print_t *jsop)
{
Expand Down Expand Up @@ -4456,7 +4592,7 @@ findjsobjects_constructor(findjsobjects_obj_t *obj)
v8_silent++;

if (read_heap_ptr(&map, addr, V8_OFF_HEAPOBJECT_MAP) != 0 ||
read_heap_ptr(&addr, map, V8_OFF_MAP_CONSTRUCTOR) != 0)
get_map_constructor(&addr, map) != 0)
goto out;

if (read_typebyte(&type, addr) != 0)
Expand Down Expand Up @@ -4546,6 +4682,7 @@ findjsobjects_range(findjsobjects_state_t *fjs, uintptr_t addr, uintptr_t size)
findjsobjects_stats_t *stats = &fjs->fjs_stats;
uint8_t type;
int jsobject = V8_TYPE_JSOBJECT, jsarray = V8_TYPE_JSARRAY;
int jstypedarray = V8_TYPE_JSTYPEDARRAY;
int jsfunction = V8_TYPE_JSFUNCTION;
caddr_t range = mdb_alloc(size, UM_SLEEP);
uintptr_t base = addr, mapaddr;
Expand Down Expand Up @@ -4590,14 +4727,14 @@ findjsobjects_range(findjsobjects_state_t *fjs, uintptr_t addr, uintptr_t size)
continue;
}

if (type != jsobject && type != jsarray)
if (type != jsobject && type != jsarray && type != jstypedarray)
continue;

stats->fjss_jsobjs++;

fjs->fjs_current = findjsobjects_alloc(addr);

if (type == jsobject) {
if (type == jsobject || type == jstypedarray) {
if (jsobj_properties(addr,
findjsobjects_prop, fjs,
&fjs->fjs_current->fjso_propinfo) != 0) {
Expand Down Expand Up @@ -5294,12 +5431,23 @@ dcmd_nodebuffer(uintptr_t addr, uint_t flags, int argc,
if (obj_jsconstructor(addr, &bufp, &len, B_FALSE) != 0)
return (DCMD_ERR);

if (strcmp(buf, "Buffer") != 0) {
if (strcmp(buf, "Buffer") != 0 &&
strcmp(buf, "Uint8Array") != 0) {
mdb_warn("%p does not appear to be a buffer\n", addr);
return (DCMD_ERR);
}
}

/*
* This works for Buffer instance in node < 4.0 because they use
* elements slots to reference the backing storage. It also works
* with Buffer in node >= 4.0 because they actually are typed arrays
* and typed arrays use elements slots to store the external data.
* We could use the "backing_store" member of the JSArrayBuffer
* associated to a typed array instead, but using elements for
* both "old" Buffer instances and new ones has the benefit of
* being able to reuse more code.
*/
if (read_heap_ptr(&elts, addr, V8_OFF_JSOBJECT_ELEMENTS) != 0)
return (DCMD_ERR);

Expand Down
8 changes: 6 additions & 2 deletions test/standalone/tst.postmortem_details.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ gcore.on('exit', function (code) {

var mdb = spawn('mdb', args, { stdio: 'pipe' });

mdb.stdin.on('end', function (code2) {
mdb.on('exit', function (code2) {
unlinkSync(tmpfile);
var retained = '; core retained as ' + corefile;

Expand Down Expand Up @@ -117,7 +117,11 @@ gcore.on('exit', function (code) {
var verifiers = [];
var buffer;
verifiers.push(function verifyConstructor(testlines) {
assert.deepEqual(testlines, [ 'Buffer' ]);
if (process.versions.node < '4.0') {
assert.deepEqual(testlines, [ 'Buffer' ]);
} else {
assert.deepEqual(testlines, [ 'Uint8Array' ]);
}
});
verifiers.push(function verifyNodebuffer(testlines) {
assert.equal(testlines.length, 1);
Expand Down
2 changes: 1 addition & 1 deletion test/standalone/tst.postmortem_findjsobjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ gcore.on('exit', function (code) {

var mdb = spawn('mdb', args, { stdio: 'pipe' });

mdb.stdin.on('end', function (code2) {
mdb.on('exit', function (code2) {
var retained = '; core retained as ' + corefile;

if (code2 != 0) {
Expand Down

0 comments on commit c5496b6

Please sign in to comment.