Skip to content

Commit 807950e

Browse files
author
David Pacheco
committed
#111 want ::v8whatis
#112 stack corruption in jsobj_properties() Reviewed by: Julien Gilli <[email protected]> Approved by: Julien Gilli <[email protected]>
1 parent d03b66d commit 807950e

File tree

7 files changed

+739
-68
lines changed

7 files changed

+739
-68
lines changed

CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
## Unreleased changes
1414

15-
None.
15+
* #111 want `::v8whatis`
16+
* #112 stack corruption in jsobj_properties()
1617

1718
## v1.3.0 (2018-02-09)
1819

docs/usage.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,8 @@ Walking V8 structures:
10421042
* v8scopeinfo: print information about a V8 ScopeInfo object
10431043
* v8str: print the contents of a V8 string (optionally show details of structure)
10441044
* v8type: print the V8 type of a heap object
1045+
* v8whatis: print information about any V8 heap object containing the given
1046+
address
10451047

10461048
Modifying configuration:
10471049

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"description": "package used only to install devDependencies for mdb_v8",
55
"private": true,
66
"devDependencies": {
7+
"jsprim": "^2.0.0",
78
"vasync": "^2.2.0",
89
"verror": "^1.10.0"
910
}

src/mdb_v8.c

Lines changed: 253 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,92 @@ obj_print_class(uintptr_t addr, v8_class_t *clp)
22512251
return (rv);
22522252
}
22532253

2254+
/*
2255+
* Attempts to determine whether the object at "addr" might contain the address
2256+
* "target". This is used for low-level heuristic analysis. Note that it's
2257+
* possible that we cannot tell whether the address is contained (e.g., if this
2258+
* is a variable-length object and we can't read how big it is).
2259+
*/
2260+
static int
2261+
obj_contains(uintptr_t addr, uint8_t type, uintptr_t target,
2262+
boolean_t *containsp, int memflags)
2263+
{
2264+
size_t size;
2265+
uintptr_t objsize;
2266+
2267+
/*
2268+
* For sequential strings, we need to look at how many characters there
2269+
* are, and how many bytes per character are used to encode the string.
2270+
* For other types of strings, the V8 heap object is not variable-sized,
2271+
* so we can treat it like the other cases below.
2272+
*/
2273+
if (V8_TYPE_STRING(type) && V8_STRREP_SEQ(type)) {
2274+
v8string_t *strp;
2275+
size_t length;
2276+
2277+
if ((strp = v8string_load(addr, memflags)) == NULL) {
2278+
return (-1);
2279+
}
2280+
2281+
length = v8string_length(strp);
2282+
2283+
if (V8_STRENC_ASCII(type)) {
2284+
size = V8_OFF_SEQASCIISTR_CHARS + length;
2285+
} else {
2286+
size = V8_OFF_SEQTWOBYTESTR_CHARS + (2 * length);
2287+
}
2288+
2289+
v8string_free(strp);
2290+
*containsp = target < addr + size;
2291+
return (0);
2292+
}
2293+
2294+
if (type == V8_TYPE_FIXEDARRAY) {
2295+
v8fixedarray_t *arrayp;
2296+
size_t length;
2297+
2298+
if ((arrayp = v8fixedarray_load(addr, memflags)) == NULL) {
2299+
return (-1);
2300+
}
2301+
2302+
length = v8fixedarray_length(arrayp);
2303+
size = V8_OFF_FIXEDARRAY_DATA + length * sizeof (uintptr_t);
2304+
v8fixedarray_free(arrayp);
2305+
*containsp = target < addr + size;
2306+
return (0);
2307+
}
2308+
2309+
if (read_size(&objsize, addr) != 0) {
2310+
return (-1);
2311+
}
2312+
2313+
size = objsize;
2314+
if (type == V8_TYPE_JSOBJECT) {
2315+
/*
2316+
* Instances of JSObject can also contain a number of property
2317+
* values directly in the object. To find out how many, we need
2318+
* to read the count out of the map. See jsobj_properties() for
2319+
* details on how this works.
2320+
*/
2321+
uintptr_t map;
2322+
uint8_t ninprops;
2323+
if (mdb_vread(&map, sizeof (map),
2324+
addr + V8_OFF_HEAPOBJECT_MAP) == -1) {
2325+
return (-1);
2326+
}
2327+
2328+
if (mdb_vread(&ninprops, sizeof (ninprops),
2329+
map + V8_OFF_MAP_INOBJECT_PROPERTIES) == -1) {
2330+
return (-1);
2331+
}
2332+
2333+
size += ninprops * sizeof (uintptr_t);
2334+
}
2335+
2336+
*containsp = target < addr + size;
2337+
return (0);
2338+
}
2339+
22542340
/*
22552341
* Print the ASCII string for the given JS string, expanding ConsStrings and
22562342
* ExternalStrings as needed.
@@ -2702,7 +2788,7 @@ jsobj_properties(uintptr_t addr,
27022788
*/
27032789
if (read_size(&size, addr) != 0)
27042790
size = 0;
2705-
if (mdb_vread(&ninprops, ps,
2791+
if (mdb_vread(&ninprops, sizeof (ninprops),
27062792
map + V8_OFF_MAP_INOBJECT_PROPERTIES) == -1)
27072793
goto err;
27082794

@@ -6763,6 +6849,170 @@ dcmd_v8warnings(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
67636849
return (DCMD_OK);
67646850
}
67656851

6852+
/*
6853+
* "v8whatis" scours the memory just prior to the given address looking for
6854+
* structure that indicates a V8 heap object. This is a heuristic way to find
6855+
* the V8 heap object containing a specific address.
6856+
*/
6857+
static int
6858+
dcmd_v8whatis(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
6859+
{
6860+
uintptr_t origaddr, curaddr, curvalue, ptrlowbits;
6861+
size_t curoffset, maxoffset = 4096;
6862+
boolean_t contained, verbose = B_FALSE;
6863+
uint8_t typebyte;
6864+
6865+
if (!(flags & DCMD_ADDRSPEC)) {
6866+
mdb_warn("must specify address for ::v8whatis\n");
6867+
return (DCMD_ERR);
6868+
}
6869+
6870+
if (mdb_getopts(argc, argv,
6871+
'v', MDB_OPT_SETBITS, B_TRUE, &verbose,
6872+
'd', MDB_OPT_UINTPTR, &maxoffset, NULL) != argc) {
6873+
return (DCMD_USAGE);
6874+
}
6875+
6876+
if (maxoffset > INT16_MAX) {
6877+
mdb_warn("warn: very large value supplied for \"-d\": %u\n",
6878+
maxoffset);
6879+
}
6880+
6881+
origaddr = addr;
6882+
6883+
/*
6884+
* Objects will always be stored at pointer-aligned addresses. If we're
6885+
* given an address that's not pointer-aligned, clear the low bits to
6886+
* find the pointer-sized value containing the address given.
6887+
*/
6888+
ptrlowbits = sizeof (uintptr_t) - 1;
6889+
addr &= ~ptrlowbits;
6890+
assert(addr <= origaddr && origaddr - addr < sizeof (uintptr_t));
6891+
6892+
/*
6893+
* On top of that, set the heap object tag bits. Recall that most
6894+
* mdb_v8 commands interpret values the same way as V8: if the tag bits
6895+
* are set, then this is a heap object; otherwise, it's not. And this
6896+
* command only makes sense for heap objects, so one might expect that
6897+
* we would bail if we're given something else. But in practice, this
6898+
* command is expected to be chained with `::ugrep` or some other
6899+
* command that reports heap objects without the tag bits set, so it
6900+
* makes sense to just assume they were supposed to be set.
6901+
*/
6902+
addr |= V8_HeapObjectTag;
6903+
if (verbose && origaddr != addr) {
6904+
mdb_warn("assuming heap object at %p\n", addr);
6905+
}
6906+
6907+
/*
6908+
* At this point, we walk backwards from the address we're given looking
6909+
* for something that looks like a V8 heap object.
6910+
*/
6911+
for (curoffset = 0; curoffset < maxoffset;
6912+
curoffset += sizeof (uintptr_t)) {
6913+
curaddr = addr - curoffset;
6914+
assert(V8_IS_HEAPOBJECT(curaddr));
6915+
6916+
if (read_heap_ptr(&curvalue, curaddr,
6917+
V8_OFF_HEAPOBJECT_MAP) != 0 ||
6918+
read_typebyte(&typebyte, curvalue) != 0) {
6919+
/*
6920+
* The address we're looking at was either unreadable,
6921+
* or we could not follow its Map pointer to find the
6922+
* type byte. This cannot be a valid heap object
6923+
* because every heap object has a Map pointer as its
6924+
* first field.
6925+
*/
6926+
continue;
6927+
}
6928+
6929+
if (typebyte != V8_TYPE_MAP) {
6930+
/*
6931+
* The address we're looking at refers to something
6932+
* other than a Map. Again, this cannot be the address
6933+
* of a valid heap object.
6934+
*/
6935+
continue;
6936+
}
6937+
6938+
/*
6939+
* We've found what looks like a valid Map object. See if we
6940+
* can read its type byte, too. If not, this is likely garbage.
6941+
*/
6942+
if (read_typebyte(&typebyte, curaddr) != 0) {
6943+
continue;
6944+
}
6945+
6946+
break;
6947+
}
6948+
6949+
if (curoffset >= maxoffset) {
6950+
if (verbose) {
6951+
mdb_warn("%p: no heap object found in previous "
6952+
"%u bytes\n", addr, maxoffset);
6953+
}
6954+
return (DCMD_OK);
6955+
}
6956+
6957+
/*
6958+
* At this point, check to see if the address that we were given might
6959+
* be contained in this object. If not, that means we found a Map for a
6960+
* heap object that doesn't contain our target address. We could have
6961+
* checked this in the loop above so that we'd keep walking backwards in
6962+
* this case, but we assume that Map objects aren't likely to appear
6963+
* inside the middle of other valid objects, and thus that if we found a
6964+
* Map and its heap object doesn't contain our target address, then
6965+
* we're done -- there is no heap object containing our target.
6966+
*/
6967+
if (obj_contains(curaddr, typebyte, addr, &contained,
6968+
UM_SLEEP | UM_GC) == 0 && !contained) {
6969+
if (verbose) {
6970+
mdb_warn("%p: heap object found at %p "
6971+
"(%p-0x%x, type %s) does not appear to contain "
6972+
"%p\n", addr, curaddr, addr, curoffset,
6973+
enum_lookup_str(v8_types, typebyte, "(unknown)"),
6974+
addr);
6975+
}
6976+
return (DCMD_OK);
6977+
}
6978+
6979+
if (!verbose) {
6980+
mdb_printf("%p\n", curaddr);
6981+
return (DCMD_OK);
6982+
}
6983+
6984+
mdb_printf("%p (found Map at %p (%p-0x%x) for type %s)", curaddr,
6985+
curaddr, origaddr, origaddr - curaddr,
6986+
enum_lookup_str(v8_types, typebyte, "(unknown)"));
6987+
return (DCMD_OK);
6988+
}
6989+
6990+
static void
6991+
dcmd_v8whatis_help(void)
6992+
{
6993+
mdb_printf("%s\n\n",
6994+
"Given an address, attempt to determine what V8 heap object, if any,\n"
6995+
"contains the address. V8 heap objects have a reasonably consistent header\n"
6996+
"structure. This command walks back from the given address looking for this\n"
6997+
"structure. This is believed to be reasonably reliable, but it's ultimately\n"
6998+
"heuristic and may produce the wrong output.\n"
6999+
"\n"
7000+
"Note that unlike other dcmds, this command accepts untagged heap addresses\n"
7001+
"(which would normally be considered non-heap, small integer values) and \n"
7002+
"implicitly adds the tag, allowing it to be more easily used with ::ugrep.\n");
7003+
7004+
mdb_dec_indent(2);
7005+
mdb_printf("%<b>OPTIONS%</b>\n");
7006+
mdb_inc_indent(2);
7007+
7008+
mdb_printf("%s\n",
7009+
" -v Verbose mode -- print details about any matches found\n"
7010+
" (or why a found match was not reported)\n"
7011+
" -d BYTES Scan up to BYTES bytes below the initial target. Default: 4096\n");
7012+
}
7013+
7014+
7015+
67667016
typedef struct jselement_walk_data {
67677017
mdb_walk_state_t *jsew_wsp;
67687018
int jsew_memflags;
@@ -7039,6 +7289,8 @@ static const mdb_dcmd_t v8_mdb_dcmds[] = {
70397289
dcmd_v8types },
70407290
{ "v8warnings", NULL, "toggle V8 warnings",
70417291
dcmd_v8warnings },
7292+
{ "v8whatis", NULL, "attempt to identify containing V8 heap object",
7293+
dcmd_v8whatis, dcmd_v8whatis_help },
70427294

70437295
{ NULL }
70447296
};

0 commit comments

Comments
 (0)