@@ -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+
67667016typedef 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