@@ -2251,6 +2251,92 @@ obj_print_class(uintptr_t addr, v8_class_t *clp)
2251
2251
return (rv );
2252
2252
}
2253
2253
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
+
2254
2340
/*
2255
2341
* Print the ASCII string for the given JS string, expanding ConsStrings and
2256
2342
* ExternalStrings as needed.
@@ -2702,7 +2788,7 @@ jsobj_properties(uintptr_t addr,
2702
2788
*/
2703
2789
if (read_size (& size , addr ) != 0 )
2704
2790
size = 0 ;
2705
- if (mdb_vread (& ninprops , ps ,
2791
+ if (mdb_vread (& ninprops , sizeof ( ninprops ) ,
2706
2792
map + V8_OFF_MAP_INOBJECT_PROPERTIES ) == -1 )
2707
2793
goto err ;
2708
2794
@@ -6763,6 +6849,170 @@ dcmd_v8warnings(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
6763
6849
return (DCMD_OK );
6764
6850
}
6765
6851
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
+
6766
7016
typedef struct jselement_walk_data {
6767
7017
mdb_walk_state_t * jsew_wsp ;
6768
7018
int jsew_memflags ;
@@ -7039,6 +7289,8 @@ static const mdb_dcmd_t v8_mdb_dcmds[] = {
7039
7289
dcmd_v8types },
7040
7290
{ "v8warnings" , NULL , "toggle V8 warnings" ,
7041
7291
dcmd_v8warnings },
7292
+ { "v8whatis" , NULL , "attempt to identify containing V8 heap object" ,
7293
+ dcmd_v8whatis , dcmd_v8whatis_help },
7042
7294
7043
7295
{ NULL }
7044
7296
};
0 commit comments