1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287
|
/*--------------------------------------------------------------------*/
/*--- The leak checker. mc_leakcheck.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of MemCheck, a heavyweight Valgrind tool for
detecting memory errors.
Copyright (C) 2000-2017 Julian Seward
jseward@acm.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
#include "pub_tool_basics.h"
#include "pub_tool_vki.h"
#include "pub_tool_aspacehl.h"
#include "pub_tool_aspacemgr.h"
#include "pub_tool_execontext.h"
#include "pub_tool_hashtable.h"
#include "pub_tool_libcbase.h"
#include "pub_tool_libcassert.h"
#include "pub_tool_libcprint.h"
#include "pub_tool_libcsignal.h"
#include "pub_tool_machine.h"
#include "pub_tool_mallocfree.h"
#include "pub_tool_options.h"
#include "pub_tool_oset.h"
#include "pub_tool_poolalloc.h"
#include "pub_tool_signals.h" // Needed for mc_include.h
#include "pub_tool_libcsetjmp.h" // setjmp facilities
#include "pub_tool_tooliface.h" // Needed for mc_include.h
#include "pub_tool_xarray.h"
#include "pub_tool_xtree.h"
#include "mc_include.h"
/*------------------------------------------------------------*/
/*--- An overview of leak checking. ---*/
/*------------------------------------------------------------*/
// Leak-checking is a directed-graph traversal problem. The graph has
// two kinds of nodes:
// - root-set nodes:
// - GP registers of all threads;
// - valid, aligned, pointer-sized data words in valid client memory,
// including stacks, but excluding words within client heap-allocated
// blocks (they are excluded so that later on we can differentiate
// between heap blocks that are indirectly leaked vs. directly leaked).
// - heap-allocated blocks. A block is a mempool chunk or a malloc chunk
// that doesn't contain a mempool chunk. Nb: the terms "blocks" and
// "chunks" are used interchangeably below.
//
// There are two kinds of edges:
// - start-pointers, i.e. pointers to the start of a block;
// - interior-pointers, i.e. pointers to the interior of a block.
//
// We use "pointers" rather than "edges" below.
//
// Root set nodes only point to blocks. Blocks only point to blocks;
// a block can point to itself.
//
// The aim is to traverse the graph and determine the status of each block.
//
// There are 9 distinct cases. See memcheck/docs/mc-manual.xml for details.
// Presenting all nine categories to the user is probably too much.
// Currently we do this:
// - definitely lost: case 3
// - indirectly lost: case 4, 9
// - possibly lost: cases 5..8
// - still reachable: cases 1, 2
//
// It's far from clear that this is the best possible categorisation; it's
// accreted over time without any central guiding principle.
/*------------------------------------------------------------*/
/*--- XXX: Thoughts for improvement. ---*/
/*------------------------------------------------------------*/
// From the user's point of view:
// - If they aren't using interior-pointers, they just have to fix the
// directly lost blocks, and the indirectly lost ones will be fixed as
// part of that. Any possibly lost blocks will just be due to random
// pointer garbage and can be ignored.
//
// - If they are using interior-pointers, the fact that they currently are not
// being told which ones might be directly lost vs. indirectly lost makes
// it hard to know where to begin.
//
// All this makes me wonder if new option is warranted:
// --follow-interior-pointers. By default it would be off, the leak checker
// wouldn't follow interior-pointers and there would only be 3 categories:
// R, DL, IL.
//
// If turned on, then it would show 7 categories (R, DL, IL, DR/DL, IR/IL,
// IR/IL/DL, IL/DL). That output is harder to understand but it's your own
// damn fault for using interior-pointers...
//
// ----
//
// Also, why are two blank lines printed between each loss record?
// [bug 197930]
//
// ----
//
// Also, --show-reachable is a bad name because it also turns on the showing
// of indirectly leaked blocks(!) It would be better named --show-all or
// --show-all-heap-blocks, because that's the end result.
// We now have the option --show-leak-kinds=... which allows to specify =all.
//
// ----
//
// Also, the VALGRIND_LEAK_CHECK and VALGRIND_QUICK_LEAK_CHECK aren't great
// names. VALGRIND_FULL_LEAK_CHECK and VALGRIND_SUMMARY_LEAK_CHECK would be
// better.
//
// ----
//
// Also, VALGRIND_COUNT_LEAKS and VALGRIND_COUNT_LEAK_BLOCKS aren't great as
// they combine direct leaks and indirect leaks into one. New, more precise
// ones (they'll need new names) would be good. If more categories are
// used, as per the --follow-interior-pointers option, they should be
// updated accordingly. And they should use a struct to return the values.
//
// ----
//
// Also, for this case:
//
// (4) p4 BBB ---> AAA
//
// BBB is definitely directly lost. AAA is definitely indirectly lost.
// Here's the relevant loss records printed for a full check (each block is
// 16 bytes):
//
// ==20397== 16 bytes in 1 blocks are indirectly lost in loss record 9 of 15
// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
// ==20397== by 0x400521: mk (leak-cases.c:49)
// ==20397== by 0x400578: main (leak-cases.c:72)
//
// ==20397== 32 (16 direct, 16 indirect) bytes in 1 blocks are definitely
// lost in loss record 14 of 15
// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
// ==20397== by 0x400521: mk (leak-cases.c:49)
// ==20397== by 0x400580: main (leak-cases.c:72)
//
// The first one is fine -- it describes AAA.
//
// The second one is for BBB. It's correct in that 16 bytes in 1 block are
// directly lost. It's also correct that 16 are indirectly lost as a result,
// but it means that AAA is being counted twice in the loss records. (It's
// not, thankfully, counted twice in the summary counts). Argh.
//
// This would be less confusing for the second one:
//
// ==20397== 16 bytes in 1 blocks are definitely lost in loss record 14
// of 15 (and 16 bytes in 1 block are indirectly lost as a result; they
// are mentioned elsewhere (if --show-reachable=yes or indirect is given
// in --show-leak-kinds=... !))
// ==20397== at 0x4C2694E: malloc (vg_replace_malloc.c:177)
// ==20397== by 0x400521: mk (leak-cases.c:49)
// ==20397== by 0x400580: main (leak-cases.c:72)
//
// But ideally we'd present the loss record for the directly lost block and
// then the resultant indirectly lost blocks and make it clear the
// dependence. Double argh.
/*------------------------------------------------------------*/
/*--- The actual algorithm. ---*/
/*------------------------------------------------------------*/
// - Find all the blocks (a.k.a. chunks) to check. Mempool chunks require
// some special treatment because they can be within malloc'd blocks.
// - Scan every word in the root set (GP registers and valid
// non-heap memory words).
// - First, we skip if it doesn't point to valid memory.
// - Then, we see if it points to the start or interior of a block. If
// so, we push the block onto the mark stack and mark it as having been
// reached.
// - Then, we process the mark stack, repeating the scanning for each block;
// this can push more blocks onto the mark stack. We repeat until the
// mark stack is empty. Each block is marked as definitely or possibly
// reachable, depending on whether interior-pointers were required to
// reach it.
// - At this point we know for every block if it's reachable or not.
// - We then push each unreached block onto the mark stack, using the block
// number as the "clique" number.
// - We process the mark stack again, this time grouping blocks into cliques
// in order to facilitate the directly/indirectly lost categorisation.
// - We group blocks by their ExeContexts and categorisation, and print them
// if --leak-check=full. We also print summary numbers.
//
// A note on "cliques":
// - A directly lost block is one with no pointers to it. An indirectly
// lost block is one that is pointed to by a directly or indirectly lost
// block.
// - Each directly lost block has zero or more indirectly lost blocks
// hanging off it. All these blocks together form a "clique". The
// directly lost block is called the "clique leader". The clique number
// is the number (in lc_chunks[]) of the clique leader.
// - Actually, a directly lost block may be pointed to if it's part of a
// cycle. In that case, there may be more than one choice for the clique
// leader, and the choice is arbitrary. Eg. if you have A-->B and B-->A
// either A or B could be the clique leader.
// - Cliques cannot overlap, and will be truncated to avoid this. Eg. if we
// have A-->C and B-->C, the two cliques will be {A,C} and {B}, or {A} and
// {B,C} (again the choice is arbitrary). This is because we don't want
// to count a block as indirectly lost more than once.
//
// A note on 'is_prior_definite':
// - This is a boolean used in various places that indicates if the chain
// up to the prior node (prior to the one being considered) is definite.
// - In the clique == -1 case:
// - if True it means that the prior node is a root-set node, or that the
// prior node is a block which is reachable from the root-set via
// start-pointers.
// - if False it means that the prior node is a block that is only
// reachable from the root-set via a path including at least one
// interior-pointer.
// - In the clique != -1 case, currently it's always True because we treat
// start-pointers and interior-pointers the same for direct/indirect leak
// checking. If we added a PossibleIndirectLeak state then this would
// change.
// Define to debug the memory-leak-detector.
#define VG_DEBUG_FIND_CHUNK 0
#define VG_DEBUG_LEAKCHECK 0
#define VG_DEBUG_CLIQUE 0
/*------------------------------------------------------------*/
/*--- Getting the initial chunks, and searching them. ---*/
/*------------------------------------------------------------*/
// Compare the MC_Chunks by 'data' (i.e. the address of the block).
static Int compare_MC_Chunks(const void* n1, const void* n2)
{
const MC_Chunk* mc1 = *(const MC_Chunk *const *)n1;
const MC_Chunk* mc2 = *(const MC_Chunk *const *)n2;
if (mc1->data < mc2->data) return -1;
if (mc1->data > mc2->data) return 1;
return 0;
}
#if VG_DEBUG_FIND_CHUNK
// Used to sanity-check the fast binary-search mechanism.
static
Int find_chunk_for_OLD ( Addr ptr,
MC_Chunk** chunks,
Int n_chunks )
{
Int i;
Addr a_lo, a_hi;
PROF_EVENT(MCPE_FIND_CHUNK_FOR_OLD);
for (i = 0; i < n_chunks; i++) {
PROF_EVENT(MCPE_FIND_CHUNK_FOR_OLD_LOOP);
a_lo = chunks[i]->data;
a_hi = ((Addr)chunks[i]->data) + chunks[i]->szB;
if (a_lo == a_hi)
a_hi++; // Special case for szB 0. See find_chunk_for.
if (a_lo <= ptr && ptr < a_hi)
return i;
}
return -1;
}
#endif
// Find the i such that ptr points at or inside the block described by
// chunks[i]. Return -1 if none found. This assumes that chunks[]
// has been sorted on the 'data' field.
static
Int find_chunk_for ( Addr ptr,
MC_Chunk** chunks,
Int n_chunks )
{
Addr a_mid_lo, a_mid_hi;
Int lo, mid, hi, retVal;
// VG_(printf)("find chunk for %p = ", ptr);
retVal = -1;
lo = 0;
hi = n_chunks-1;
while (True) {
// Invariant: current unsearched space is from lo to hi, inclusive.
if (lo > hi) break; // not found
mid = (lo + hi) / 2;
a_mid_lo = chunks[mid]->data;
a_mid_hi = chunks[mid]->data + chunks[mid]->szB;
// Extent of block 'mid' is [a_mid_lo .. a_mid_hi).
// Special-case zero-sized blocks - treat them as if they had
// size 1. Not doing so causes them to not cover any address
// range at all and so will never be identified as the target of
// any pointer, which causes them to be incorrectly reported as
// definitely leaked.
if (chunks[mid]->szB == 0)
a_mid_hi++;
if (ptr < a_mid_lo) {
hi = mid-1;
continue;
}
if (ptr >= a_mid_hi) {
lo = mid+1;
continue;
}
tl_assert(ptr >= a_mid_lo && ptr < a_mid_hi);
retVal = mid;
break;
}
# if VG_DEBUG_FIND_CHUNK
tl_assert(retVal == find_chunk_for_OLD ( ptr, chunks, n_chunks ));
# endif
// VG_(printf)("%d\n", retVal);
return retVal;
}
static MC_Chunk**
find_active_chunks(Int* pn_chunks)
{
// Our goal is to construct a set of chunks that includes every
// mempool chunk, and every malloc region that *doesn't* contain a
// mempool chunk.
MC_Mempool *mp;
MC_Chunk **mallocs, **chunks, *mc;
UInt n_mallocs, n_chunks, m, s;
Bool *malloc_chunk_holds_a_pool_chunk;
// First we collect all the malloc chunks into an array and sort it.
// We do this because we want to query the chunks by interior
// pointers, requiring binary search.
mallocs = (MC_Chunk**) VG_(HT_to_array)( MC_(malloc_list), &n_mallocs );
if (n_mallocs == 0) {
tl_assert(mallocs == NULL);
*pn_chunks = 0;
return NULL;
}
VG_(ssort)(mallocs, n_mallocs, sizeof(VgHashNode*), compare_MC_Chunks);
// Then we build an array containing a Bool for each malloc chunk,
// indicating whether it contains any mempools.
malloc_chunk_holds_a_pool_chunk = VG_(calloc)( "mc.fas.1",
n_mallocs, sizeof(Bool) );
n_chunks = n_mallocs;
// Then we loop over the mempool tables. For each chunk in each
// pool, we set the entry in the Bool array corresponding to the
// malloc chunk containing the mempool chunk.
VG_(HT_ResetIter)(MC_(mempool_list));
while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
VG_(HT_ResetIter)(mp->chunks);
while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
// We'll need to record this chunk.
n_chunks++;
// Possibly invalidate the malloc holding the beginning of this chunk.
m = find_chunk_for(mc->data, mallocs, n_mallocs);
if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
tl_assert(n_chunks > 0);
n_chunks--;
malloc_chunk_holds_a_pool_chunk[m] = True;
}
// Possibly invalidate the malloc holding the end of this chunk.
if (mc->szB > 1) {
m = find_chunk_for(mc->data + (mc->szB - 1), mallocs, n_mallocs);
if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
tl_assert(n_chunks > 0);
n_chunks--;
malloc_chunk_holds_a_pool_chunk[m] = True;
}
}
}
}
tl_assert(n_chunks > 0);
// Create final chunk array.
chunks = VG_(malloc)("mc.fas.2", sizeof(VgHashNode*) * (n_chunks));
s = 0;
// Copy the mempool chunks and the non-marked malloc chunks into a
// combined array of chunks.
VG_(HT_ResetIter)(MC_(mempool_list));
while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
VG_(HT_ResetIter)(mp->chunks);
while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
tl_assert(s < n_chunks);
chunks[s++] = mc;
}
}
for (m = 0; m < n_mallocs; ++m) {
if (!malloc_chunk_holds_a_pool_chunk[m]) {
tl_assert(s < n_chunks);
chunks[s++] = mallocs[m];
}
}
tl_assert(s == n_chunks);
// Free temporaries.
VG_(free)(mallocs);
VG_(free)(malloc_chunk_holds_a_pool_chunk);
*pn_chunks = n_chunks;
return chunks;
}
/*------------------------------------------------------------*/
/*--- The leak detector proper. ---*/
/*------------------------------------------------------------*/
// Holds extra info about each block during leak checking.
typedef
struct {
UInt state:2; // Reachedness.
UInt pending:1; // Scan pending.
UInt heuristic: (sizeof(UInt)*8)-3;
// Heuristic with which this block was considered reachable.
// LchNone if state != Reachable or no heuristic needed to
// consider it reachable.
union {
SizeT indirect_szB;
// If Unreached, how many bytes are unreachable from here.
SizeT clique;
// if IndirectLeak, clique leader to which it belongs.
} IorC;
}
LC_Extra;
// An array holding pointers to every chunk we're checking. Sorted by address.
// lc_chunks is initialised during leak search. It is kept after leak search
// to support printing the list of blocks belonging to a loss record.
// lc_chunk array can only be used validly till the next "free" operation
// (as a free operation potentially destroys one or more chunks).
// To detect lc_chunk is valid, we store the nr of frees operations done
// when lc_chunk was build : lc_chunks (and lc_extras) stays valid as
// long as no free operations has been done since lc_chunks building.
static MC_Chunk** lc_chunks;
// How many chunks we're dealing with.
static Int lc_n_chunks;
static SizeT lc_chunks_n_frees_marker;
// This has the same number of entries as lc_chunks, and each entry
// in lc_chunks corresponds with the entry here (ie. lc_chunks[i] and
// lc_extras[i] describe the same block).
static LC_Extra* lc_extras;
// chunks will be converted and merged in loss record, maintained in lr_table
// lr_table elements are kept from one leak_search to another to implement
// the "print new/changed leaks" client request
static OSet* lr_table;
// Array of sorted loss record (produced during last leak search).
static LossRecord** lr_array;
// Value of the heuristics parameter used in the current (or last) leak check.
static UInt detect_memory_leaks_last_heuristics;
// DeltaMode used the last time we called detect_memory_leaks.
// The recorded leak errors are output using a logic based on this delta_mode.
// The below avoids replicating the delta_mode in each LossRecord.
LeakCheckDeltaMode MC_(detect_memory_leaks_last_delta_mode);
// Each leak search run increments the below generation counter.
// A used suppression during a leak search will contain this
// generation number.
UInt MC_(leak_search_gen);
// Records chunks that are currently being processed. Each element in the
// stack is an index into lc_chunks and lc_extras. Its size is
// 'lc_n_chunks' because in the worst case that's how many chunks could be
// pushed onto it (actually I think the maximum is lc_n_chunks-1 but let's
// be conservative).
static Int* lc_markstack;
// The index of the top element of the stack; -1 if the stack is empty, 0 if
// the stack has one element, 1 if it has two, etc.
static Int lc_markstack_top;
// Keeps track of how many bytes of memory we've scanned, for printing.
// (Nb: We don't keep track of how many register bytes we've scanned.)
static SizeT lc_scanned_szB;
// Keeps track of how many bytes we have not scanned due to read errors that
// caused a signal such as SIGSEGV.
static SizeT lc_sig_skipped_szB;
SizeT MC_(bytes_leaked) = 0;
SizeT MC_(bytes_indirect) = 0;
SizeT MC_(bytes_dubious) = 0;
SizeT MC_(bytes_reachable) = 0;
SizeT MC_(bytes_suppressed) = 0;
SizeT MC_(blocks_leaked) = 0;
SizeT MC_(blocks_indirect) = 0;
SizeT MC_(blocks_dubious) = 0;
SizeT MC_(blocks_reachable) = 0;
SizeT MC_(blocks_suppressed) = 0;
// Subset of MC_(bytes_reachable) and MC_(blocks_reachable) which
// are considered reachable due to the corresponding heuristic.
static SizeT MC_(bytes_heuristically_reachable)[N_LEAK_CHECK_HEURISTICS]
= {0,0,0,0};
static SizeT MC_(blocks_heuristically_reachable)[N_LEAK_CHECK_HEURISTICS]
= {0,0,0,0};
// Determines if a pointer is to a chunk. Returns the chunk number et al
// via call-by-reference.
static Bool
lc_is_a_chunk_ptr(Addr ptr, Int* pch_no, MC_Chunk** pch, LC_Extra** pex)
{
Int ch_no;
MC_Chunk* ch;
LC_Extra* ex;
// Quick filter. Note: implemented with am, not with get_vabits2
// as ptr might be random data pointing anywhere. On 64 bit
// platforms, getting va bits for random data can be quite costly
// due to the secondary map.
if (!VG_(am_is_valid_for_client)(ptr, 1, VKI_PROT_READ)) {
return False;
} else {
ch_no = find_chunk_for(ptr, lc_chunks, lc_n_chunks);
tl_assert(ch_no >= -1 && ch_no < lc_n_chunks);
if (ch_no == -1) {
return False;
} else {
// Ok, we've found a pointer to a chunk. Get the MC_Chunk and its
// LC_Extra.
ch = lc_chunks[ch_no];
ex = &(lc_extras[ch_no]);
tl_assert(ptr >= ch->data);
tl_assert(ptr < ch->data + ch->szB + (ch->szB==0 ? 1 : 0));
if (VG_DEBUG_LEAKCHECK)
VG_(printf)("ptr=%#lx -> block %d\n", ptr, ch_no);
*pch_no = ch_no;
*pch = ch;
*pex = ex;
return True;
}
}
}
// Push a chunk (well, just its index) onto the mark stack.
static void lc_push(Int ch_no, MC_Chunk* ch)
{
if (!lc_extras[ch_no].pending) {
if (0) {
VG_(printf)("pushing %#lx-%#lx\n", ch->data, ch->data + ch->szB);
}
lc_markstack_top++;
tl_assert(lc_markstack_top < lc_n_chunks);
lc_markstack[lc_markstack_top] = ch_no;
tl_assert(!lc_extras[ch_no].pending);
lc_extras[ch_no].pending = True;
}
}
// Return the index of the chunk on the top of the mark stack, or -1 if
// there isn't one.
static Bool lc_pop(Int* ret)
{
if (-1 == lc_markstack_top) {
return False;
} else {
tl_assert(0 <= lc_markstack_top && lc_markstack_top < lc_n_chunks);
*ret = lc_markstack[lc_markstack_top];
lc_markstack_top--;
tl_assert(lc_extras[*ret].pending);
lc_extras[*ret].pending = False;
return True;
}
}
static const HChar* pp_heuristic(LeakCheckHeuristic h)
{
switch(h) {
case LchNone: return "none";
case LchStdString: return "stdstring";
case LchLength64: return "length64";
case LchNewArray: return "newarray";
case LchMultipleInheritance: return "multipleinheritance";
default: return "???invalid heuristic???";
}
}
// True if ptr looks like the address of a vtable, i.e. if ptr
// points to an array of pointers to functions.
// It is assumed the only caller of this function is heuristic_reachedness
// which must check that ptr is aligned and above page 0.
// Checking that ptr is above page 0 is an optimisation : it is assumed
// that no vtable is located in the page 0. So, all small integer values
// encountered during the scan will not incur the cost of calling this
// function.
static Bool aligned_ptr_above_page0_is_vtable_addr(Addr ptr)
{
// ??? If performance problem:
// ??? maybe implement a cache (array indexed by ptr % primenr)
// ??? of "I am a vtable ptr" ???
// ??? Maybe the debug info could (efficiently?) be used to detect vtables ?
// We consider ptr as a vtable ptr if it points to a table
// where we find only NULL pointers or pointers pointing at an
// executable region. We must find at least 2 non NULL pointers
// before considering ptr as a vtable pointer.
// We scan a maximum of VTABLE_MAX_CHECK words for these 2 non NULL
// pointers.
#define VTABLE_MAX_CHECK 20
NSegment const *seg;
UInt nr_fn_ptrs = 0;
Addr scan;
Addr scan_max;
// First verify ptr points inside a client mapped file section.
// ??? is a vtable always in a file mapped readable section ?
seg = VG_(am_find_nsegment) (ptr);
if (seg == NULL
|| seg->kind != SkFileC
|| !seg->hasR)
return False;
// Check potential function pointers, up to a maximum of VTABLE_MAX_CHECK.
scan_max = ptr + VTABLE_MAX_CHECK*sizeof(Addr);
// If ptr is near the end of seg, avoid scan_max exceeding the end of seg:
if (scan_max > seg->end - sizeof(Addr))
scan_max = seg->end - sizeof(Addr);
for (scan = ptr; scan <= scan_max; scan+=sizeof(Addr)) {
Addr pot_fn = *((Addr *)scan);
if (pot_fn == 0)
continue; // NULL fn pointer. Seems it can happen in vtable.
seg = VG_(am_find_nsegment) (pot_fn);
#if defined(VGA_ppc64be)
// ppc64BE uses a thunk table (function descriptors), so we have one
// more level of indirection to follow.
if (seg == NULL
|| seg->kind != SkFileC
|| !seg->hasR
|| !seg->hasW)
return False; // ptr to nowhere, or not a ptr to thunks.
pot_fn = *((Addr *)pot_fn);
if (pot_fn == 0)
continue; // NULL fn pointer. Seems it can happen in vtable.
seg = VG_(am_find_nsegment) (pot_fn);
#endif
if (seg == NULL
|| seg->kind != SkFileC
|| !seg->hasT)
return False; // ptr to nowhere, or not a fn ptr.
nr_fn_ptrs++;
if (nr_fn_ptrs == 2)
return True;
}
return False;
}
// true if a is properly aligned and points to 64bits of valid memory
static Bool is_valid_aligned_ULong ( Addr a )
{
if (sizeof(Word) == 8)
return MC_(is_valid_aligned_word)(a);
return MC_(is_valid_aligned_word)(a)
&& MC_(is_valid_aligned_word)(a + 4);
}
/* The below leak_search_fault_catcher is used to catch memory access
errors happening during leak_search. During the scan, we check
with aspacemgr and/or VA bits that each page or dereferenced location is
readable and belongs to the client. However, we still protect
against SIGSEGV and SIGBUS e.g. in case aspacemgr is desynchronised
with the real page mappings. Such a desynchronisation could happen
due to an aspacemgr bug. Note that if the application is using
mprotect(NONE), then a page can be unreadable but have addressable
and defined VA bits (see mc_main.c function mc_new_mem_mprotect).
Currently, 2 functions are dereferencing client memory during leak search:
heuristic_reachedness and lc_scan_memory.
Each such function has its own fault catcher, that will call
leak_search_fault_catcher with the proper 'who' and jmpbuf parameters. */
static volatile Addr bad_scanned_addr;
static
void leak_search_fault_catcher ( Int sigNo, Addr addr,
const HChar *who, VG_MINIMAL_JMP_BUF(jmpbuf) )
{
vki_sigset_t sigmask;
if (0)
VG_(printf)("OUCH! sig=%d addr=%#lx who=%s\n", sigNo, addr, who);
/* Signal handler runs with the signal masked.
Unmask the handled signal before longjmp-ing or return-ing.
Note that during leak search, we expect only SIGSEGV or SIGBUS
and we do not expect another occurrence until we longjmp-ed!return-ed
to resume the leak search. So, it is safe to unmask the signal
here. */
/* First get current mask (by passing NULL as first arg) */
VG_(sigprocmask)(VKI_SIG_SETMASK, NULL, &sigmask);
/* Then set a new sigmask, with this signal removed from the mask. */
VG_(sigdelset)(&sigmask, sigNo);
VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL);
if (sigNo == VKI_SIGSEGV || sigNo == VKI_SIGBUS) {
bad_scanned_addr = addr;
VG_MINIMAL_LONGJMP(jmpbuf);
} else {
/* ??? During leak search, we are not supposed to receive any
other sync signal that these 2.
In theory, we should not call VG_(umsg) in a signal handler,
but better (try to) report this unexpected behaviour. */
VG_(umsg)("leak_search_fault_catcher:"
" unexpected signal %d, catcher %s ???\n",
sigNo, who);
}
}
// jmpbuf and fault_catcher used during heuristic_reachedness
static VG_MINIMAL_JMP_BUF(heuristic_reachedness_jmpbuf);
static
void heuristic_reachedness_fault_catcher ( Int sigNo, Addr addr )
{
leak_search_fault_catcher (sigNo, addr,
"heuristic_reachedness_fault_catcher",
heuristic_reachedness_jmpbuf);
}
// If ch is heuristically reachable via an heuristic member of heur_set,
// returns this heuristic.
// If ch cannot be considered reachable using one of these heuristics,
// return LchNone.
// This should only be called when ptr is an interior ptr to ch.
// The StdString/NewArray/MultipleInheritance heuristics are directly
// inspired from DrMemory:
// see http://www.burningcutlery.com/derek/docs/drmem-CGO11.pdf [section VI,C]
// and bug 280271.
static LeakCheckHeuristic heuristic_reachedness (Addr ptr,
MC_Chunk *ch, LC_Extra *ex,
UInt heur_set)
{
fault_catcher_t prev_catcher;
prev_catcher = VG_(set_fault_catcher)(heuristic_reachedness_fault_catcher);
// See leak_search_fault_catcher
if (VG_MINIMAL_SETJMP(heuristic_reachedness_jmpbuf) != 0) {
VG_(set_fault_catcher) (prev_catcher);
return LchNone;
}
if (HiS(LchStdString, heur_set)) {
// Detects inner pointers to Std::String for layout being
// length capacity refcount char_array[] \0
// where ptr points to the beginning of the char_array.
// Note: we check definedness for length and capacity but
// not for refcount, as refcount size might be smaller than
// a SizeT, giving a uninitialised hole in the first 3 SizeT.
if ( ptr == ch->data + 3 * sizeof(SizeT)
&& MC_(is_valid_aligned_word)(ch->data + sizeof(SizeT))) {
const SizeT capacity = *((SizeT*)(ch->data + sizeof(SizeT)));
if (3 * sizeof(SizeT) + capacity + 1 == ch->szB
&& MC_(is_valid_aligned_word)(ch->data)) {
const SizeT length = *((SizeT*)ch->data);
if (length <= capacity) {
// ??? could check there is no null byte from ptr to ptr+length-1
// ??? and that there is a null byte at ptr+length.
// ???
// ??? could check that ch->allockind is MC_AllocNew ???
// ??? probably not a good idea, as I guess stdstring
// ??? allocator can be done via custom allocator
// ??? or even a call to malloc ????
VG_(set_fault_catcher) (prev_catcher);
return LchStdString;
}
}
}
}
if (HiS(LchLength64, heur_set)) {
// Detects inner pointers that point at 64bit offset (8 bytes) into a
// block following the length of the remaining as 64bit number
// (=total block size - 8).
// This is used e.g. by sqlite for tracking the total size of allocated
// memory.
// Note that on 64bit platforms, a block matching LchLength64 will
// also be matched by LchNewArray.
if ( ptr == ch->data + sizeof(ULong)
&& is_valid_aligned_ULong(ch->data)) {
const ULong size = *((ULong*)ch->data);
if (size > 0 && (ch->szB - sizeof(ULong)) == size) {
VG_(set_fault_catcher) (prev_catcher);
return LchLength64;
}
}
}
if (HiS(LchNewArray, heur_set)) {
// Detects inner pointers at second word of new[] array, following
// a plausible nr of elements.
// Such inner pointers are used for arrays of elements
// having a destructor, as the delete[] of the array must know
// how many elements to destroy.
//
// We have a strange/wrong case for 'ptr = new MyClass[0];' :
// For such a case, the returned ptr points just outside the
// allocated chunk. This chunk is then seen as a definite
// leak by Valgrind, as it is not considered an interior pointer.
// It is the c++ equivalent of bug 99923 (malloc(0) wrongly considered
// as definitely leaked). See the trick in find_chunk_for handling
// 0-sized block. This trick does not work for 'new MyClass[0]'
// because a chunk "word-sized" is allocated to store the (0) nr
// of elements.
if ( ptr == ch->data + sizeof(SizeT)
&& MC_(is_valid_aligned_word)(ch->data)) {
const SizeT nr_elts = *((SizeT*)ch->data);
if (nr_elts > 0 && (ch->szB - sizeof(SizeT)) % nr_elts == 0) {
// ??? could check that ch->allockind is MC_AllocNewVec ???
VG_(set_fault_catcher) (prev_catcher);
return LchNewArray;
}
}
}
if (HiS(LchMultipleInheritance, heur_set)) {
// Detect inner pointer used for multiple inheritance.
// Assumption is that the vtable pointers are before the object.
if (VG_IS_WORD_ALIGNED(ptr)
&& MC_(is_valid_aligned_word)(ptr)) {
Addr first_addr;
Addr inner_addr;
// Avoid the call to is_vtable_addr when the addr is not
// aligned or points in the page0, as it is unlikely
// a vtable is located in this page. This last optimisation
// avoids to call aligned_ptr_above_page0_is_vtable_addr
// for all small integers.
// Note: we could possibly also avoid calling this function
// for small negative integers, as no vtable should be located
// in the last page.
inner_addr = *((Addr*)ptr);
if (VG_IS_WORD_ALIGNED(inner_addr)
&& inner_addr >= (Addr)VKI_PAGE_SIZE
&& MC_(is_valid_aligned_word)(ch->data)) {
first_addr = *((Addr*)ch->data);
if (VG_IS_WORD_ALIGNED(first_addr)
&& first_addr >= (Addr)VKI_PAGE_SIZE
&& aligned_ptr_above_page0_is_vtable_addr(inner_addr)
&& aligned_ptr_above_page0_is_vtable_addr(first_addr)) {
// ??? could check that ch->allockind is MC_AllocNew ???
VG_(set_fault_catcher) (prev_catcher);
return LchMultipleInheritance;
}
}
}
}
VG_(set_fault_catcher) (prev_catcher);
return LchNone;
}
// If 'ptr' is pointing to a heap-allocated block which hasn't been seen
// before, push it onto the mark stack.
static void
lc_push_without_clique_if_a_chunk_ptr(Addr ptr, Bool is_prior_definite)
{
Int ch_no;
MC_Chunk* ch;
LC_Extra* ex;
Reachedness ch_via_ptr; // Is ch reachable via ptr, and how ?
if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
return;
if (ex->state == Reachable) {
if (ex->heuristic && ptr == ch->data)
// If block was considered reachable via an heuristic, and it is now
// directly reachable via ptr, clear the heuristic field.
ex->heuristic = LchNone;
return;
}
// Possibly upgrade the state, ie. one of:
// - Unreached --> Possible
// - Unreached --> Reachable
// - Possible --> Reachable
if (ptr == ch->data)
ch_via_ptr = Reachable;
else if (detect_memory_leaks_last_heuristics) {
ex->heuristic
= heuristic_reachedness (ptr, ch, ex,
detect_memory_leaks_last_heuristics);
if (ex->heuristic)
ch_via_ptr = Reachable;
else
ch_via_ptr = Possible;
} else
ch_via_ptr = Possible;
if (ch_via_ptr == Reachable && is_prior_definite) {
// 'ptr' points to the start of the block or is to be considered as
// pointing to the start of the block, and the prior node is
// definite, which means that this block is definitely reachable.
ex->state = Reachable;
// State has changed to Reachable so (re)scan the block to make
// sure any blocks it points to are correctly marked.
lc_push(ch_no, ch);
} else if (ex->state == Unreached) {
// Either 'ptr' is a interior-pointer, or the prior node isn't definite,
// which means that we can only mark this block as possibly reachable.
ex->state = Possible;
// State has changed to Possible so (re)scan the block to make
// sure any blocks it points to are correctly marked.
lc_push(ch_no, ch);
}
}
static void
lc_push_if_a_chunk_ptr_register(ThreadId tid, const HChar* regname, Addr ptr)
{
lc_push_without_clique_if_a_chunk_ptr(ptr, /*is_prior_definite*/True);
}
// If ptr is pointing to a heap-allocated block which hasn't been seen
// before, push it onto the mark stack. Clique is the index of the
// clique leader.
static void
lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique, Int cur_clique)
{
Int ch_no;
MC_Chunk* ch;
LC_Extra* ex;
tl_assert(0 <= clique && clique < lc_n_chunks);
if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
return;
// If it's not Unreached, it's already been handled so ignore it.
// If ch_no==clique, it's the clique leader, which means this is a cyclic
// structure; again ignore it because it's already been handled.
if (ex->state == Unreached && ch_no != clique) {
// Note that, unlike reachable blocks, we currently don't distinguish
// between start-pointers and interior-pointers here. We probably
// should, though.
lc_push(ch_no, ch);
// Add the block to the clique, and add its size to the
// clique-leader's indirect size. Also, if the new block was
// itself a clique leader, it isn't any more, so add its
// indirect_szB to the new clique leader.
if (VG_DEBUG_CLIQUE) {
if (ex->IorC.indirect_szB > 0)
VG_(printf)(" clique %d joining clique %d adding %lu+%lu\n",
ch_no, clique, (SizeT)ch->szB, ex->IorC.indirect_szB);
else
VG_(printf)(" block %d joining clique %d adding %lu\n",
ch_no, clique, (SizeT)ch->szB);
}
lc_extras[clique].IorC.indirect_szB += ch->szB;
lc_extras[clique].IorC.indirect_szB += ex->IorC.indirect_szB;
ex->state = IndirectLeak;
ex->IorC.clique = (SizeT) cur_clique;
}
}
static void
lc_push_if_a_chunk_ptr(Addr ptr,
Int clique, Int cur_clique, Bool is_prior_definite)
{
if (-1 == clique)
lc_push_without_clique_if_a_chunk_ptr(ptr, is_prior_definite);
else
lc_push_with_clique_if_a_chunk_ptr(ptr, clique, cur_clique);
}
static VG_MINIMAL_JMP_BUF(lc_scan_memory_jmpbuf);
static
void lc_scan_memory_fault_catcher ( Int sigNo, Addr addr )
{
leak_search_fault_catcher (sigNo, addr,
"lc_scan_memory_fault_catcher",
lc_scan_memory_jmpbuf);
}
// lc_scan_memory has 2 modes:
//
// 1. Leak check mode (searched == 0).
// -----------------------------------
// Scan a block of memory between [start, start+len). This range may
// be bogus, inaccessible, or otherwise strange; we deal with it. For each
// valid aligned word we assume it's a pointer to a chunk a push the chunk
// onto the mark stack if so.
// clique is the "highest level clique" in which indirectly leaked blocks have
// to be collected. cur_clique is the current "lower" level clique through which
// the memory to be scanned has been found.
// Example: in the below tree if A is leaked, the top level clique will
// be A, while lower level cliques will be B and C.
/*
A
/ \
B C
/ \ / \
D E F G
*/
// Proper handling of top and lowest level clique allows block_list of a loss
// record to describe the hierarchy of indirectly leaked blocks.
//
// 2. Search ptr mode (searched != 0).
// -----------------------------------
// In this mode, searches for pointers to a specific address range
// In such a case, lc_scan_memory just scans [start..start+len[ for pointers
// to searched and outputs the places where searched is found.
// It does not recursively scans the found memory.
static void
lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite,
Int clique, Int cur_clique,
Addr searched, SizeT szB)
{
/* memory scan is based on the assumption that valid pointers are aligned
on a multiple of sizeof(Addr). So, we can (and must) skip the begin and
end portions of the block if they are not aligned on sizeof(Addr):
These cannot be a valid pointer, and calls to MC_(is_valid_aligned_word)
will assert for a non aligned address. */
#if defined(VGA_s390x)
// Define ptr as volatile, as on this platform, the value of ptr
// is read in code executed via a longjmp.
volatile
#endif
Addr ptr = VG_ROUNDUP(start, sizeof(Addr));
const Addr end = VG_ROUNDDN(start+len, sizeof(Addr));
fault_catcher_t prev_catcher;
if (VG_DEBUG_LEAKCHECK)
VG_(printf)("scan %#lx-%#lx (%lu)\n", start, end, len);
prev_catcher = VG_(set_fault_catcher)(lc_scan_memory_fault_catcher);
/* Optimisation: the loop below will check for each begin
of SM chunk if the chunk is fully unaddressable. The idea is to
skip efficiently such fully unaddressable SM chunks.
So, we preferably start the loop on a chunk boundary.
If the chunk is not fully unaddressable, we might be in
an unaddressable page. Again, the idea is to skip efficiently
such unaddressable page : this is the "else" part.
We use an "else" so that two consecutive fully unaddressable
SM chunks will be skipped efficiently: first one is skipped
by this piece of code. The next SM chunk will be skipped inside
the loop. */
if ( ! MC_(is_within_valid_secondary)(ptr) ) {
// Skip an invalid SM chunk till the beginning of the next SM Chunk.
ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
} else if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) {
// else we are in a (at least partially) valid SM chunk.
// We might be in the middle of an unreadable page.
// Do a cheap check to see if it's valid;
// if not, skip onto the next page.
ptr = VG_PGROUNDUP(ptr+1); // First page is bad - skip it.
}
/* The above optimisation and below loop is based on some relationships
between VKI_PAGE_SIZE, SM_SIZE and sizeof(Addr) which are asserted in
MC_(detect_memory_leaks). */
// See leak_search_fault_catcher
if (VG_MINIMAL_SETJMP(lc_scan_memory_jmpbuf) != 0) {
// Catch read error ...
# if defined(VGA_s390x)
// For a SIGSEGV, s390 delivers the page address of the bad address.
// For a SIGBUS, old s390 kernels deliver a NULL address.
// bad_scanned_addr can thus not be used.
// So, on this platform, we always skip a full page from ptr.
// The below implies to mark ptr as volatile, as we read the value
// after a longjmp to here.
lc_sig_skipped_szB += VKI_PAGE_SIZE;
ptr = ptr + VKI_PAGE_SIZE; // Unaddressable, - skip it.
# else
// On other platforms, just skip one Addr.
lc_sig_skipped_szB += sizeof(Addr);
tl_assert(bad_scanned_addr >= VG_ROUNDUP(start, sizeof(Addr)));
tl_assert(bad_scanned_addr < VG_ROUNDDN(start+len, sizeof(Addr)));
ptr = bad_scanned_addr + sizeof(Addr); // Unaddressable, - skip it.
#endif
}
while (ptr < end) {
Addr addr;
// Skip invalid chunks.
if (UNLIKELY((ptr % SM_SIZE) == 0)) {
if (! MC_(is_within_valid_secondary)(ptr) ) {
ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
continue;
}
}
// Look to see if this page seems reasonable.
if (UNLIKELY((ptr % VKI_PAGE_SIZE) == 0)) {
if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) {
ptr += VKI_PAGE_SIZE; // Bad page - skip it.
continue;
}
}
if ( MC_(is_valid_aligned_word)(ptr) ) {
lc_scanned_szB += sizeof(Addr);
// If the below read fails, we will longjmp to the loop begin.
addr = *(Addr *)ptr;
// If we get here, the scanned word is in valid memory. Now
// let's see if its contents point to a chunk.
if (UNLIKELY(searched)) {
if (addr >= searched && addr < searched + szB) {
const DiEpoch cur_ep = VG_(current_DiEpoch)();
// The found addr is 'live', so use cur_ep to describe it.
if (addr == searched) {
VG_(umsg)("*%#lx points at %#lx\n", ptr, searched);
MC_(pp_describe_addr) (cur_ep, ptr);
} else {
Int ch_no;
MC_Chunk *ch;
LC_Extra *ex;
VG_(umsg)("*%#lx interior points at %lu bytes inside %#lx\n",
ptr, (long unsigned) addr - searched, searched);
MC_(pp_describe_addr) (cur_ep, ptr);
if (lc_is_a_chunk_ptr(addr, &ch_no, &ch, &ex) ) {
Int h;
for (h = LchStdString; h < N_LEAK_CHECK_HEURISTICS; h++) {
if (heuristic_reachedness(addr, ch, ex, H2S(h)) == h) {
VG_(umsg)("block at %#lx considered reachable "
"by ptr %#lx using %s heuristic\n",
ch->data, addr, pp_heuristic(h));
}
}
// Verify the loop above has properly scanned all
// heuristics. If the below fails, it probably means the
// LeakCheckHeuristic enum is not in sync anymore with the
// above loop and/or with N_LEAK_CHECK_HEURISTICS.
tl_assert (h == N_LEAK_CHECK_HEURISTICS);
}
}
}
} else {
lc_push_if_a_chunk_ptr(addr, clique, cur_clique, is_prior_definite);
}
} else if (0 && VG_DEBUG_LEAKCHECK) {
VG_(printf)("%#lx not valid\n", ptr);
}
ptr += sizeof(Addr);
}
VG_(set_fault_catcher)(prev_catcher);
}
// Process the mark stack until empty.
static void lc_process_markstack(Int clique)
{
Int top = -1; // shut gcc up
Bool is_prior_definite;
while (lc_pop(&top)) {
tl_assert(top >= 0 && top < lc_n_chunks);
// See comment about 'is_prior_definite' at the top to understand this.
is_prior_definite = ( Possible != lc_extras[top].state );
lc_scan_memory(lc_chunks[top]->data, lc_chunks[top]->szB,
is_prior_definite, clique, (clique == -1 ? -1 : top),
/*searched*/ 0, 0);
}
}
static Word cmp_LossRecordKey_LossRecord(const void* key, const void* elem)
{
const LossRecordKey* a = key;
const LossRecordKey* b = &(((const LossRecord*)elem)->key);
// Compare on states first because that's fast.
if (a->state < b->state) return -1;
if (a->state > b->state) return 1;
// Ok, the states are equal. Now compare the locations, which is slower.
if (VG_(eq_ExeContext)(
MC_(clo_leak_resolution), a->allocated_at, b->allocated_at))
return 0;
// Different locations. Ordering is arbitrary, just use the ec pointer.
if (a->allocated_at < b->allocated_at) return -1;
if (a->allocated_at > b->allocated_at) return 1;
VG_(tool_panic)("bad LossRecord comparison");
}
static Int cmp_LossRecords(const void* va, const void* vb)
{
const LossRecord* lr_a = *(const LossRecord *const *)va;
const LossRecord* lr_b = *(const LossRecord *const *)vb;
SizeT total_szB_a = lr_a->szB + lr_a->indirect_szB;
SizeT total_szB_b = lr_b->szB + lr_b->indirect_szB;
// First compare by sizes.
if (total_szB_a < total_szB_b) return -1;
if (total_szB_a > total_szB_b) return 1;
// If size are equal, compare by states.
if (lr_a->key.state < lr_b->key.state) return -1;
if (lr_a->key.state > lr_b->key.state) return 1;
// If they're still equal here, it doesn't matter that much, but we keep
// comparing other things so that regtests are as deterministic as
// possible. So: compare num_blocks.
if (lr_a->num_blocks < lr_b->num_blocks) return -1;
if (lr_a->num_blocks > lr_b->num_blocks) return 1;
// Finally, compare ExeContext addresses... older ones are likely to have
// lower addresses.
if (lr_a->key.allocated_at < lr_b->key.allocated_at) return -1;
if (lr_a->key.allocated_at > lr_b->key.allocated_at) return 1;
return 0;
}
// allocates or reallocates lr_array, and set its elements to the loss records
// contains in lr_table.
static UInt get_lr_array_from_lr_table(void) {
UInt i, n_lossrecords;
LossRecord* lr;
n_lossrecords = VG_(OSetGen_Size)(lr_table);
// (re-)create the array of pointers to the loss records.
// lr_array is kept to allow producing the block list from gdbserver.
if (lr_array != NULL)
VG_(free)(lr_array);
lr_array = VG_(malloc)("mc.pr.2", n_lossrecords * sizeof(LossRecord*));
i = 0;
VG_(OSetGen_ResetIter)(lr_table);
while ( (lr = VG_(OSetGen_Next)(lr_table)) ) {
lr_array[i++] = lr;
}
tl_assert(i == n_lossrecords);
return n_lossrecords;
}
static void get_printing_rules(LeakCheckParams* lcp,
LossRecord* lr,
Bool* count_as_error,
Bool* print_record)
{
// Rules for printing:
// - We don't show suppressed loss records ever (and that's controlled
// within the error manager).
// - We show non-suppressed loss records that are specified in
// --show-leak-kinds=... if --leak-check=yes.
Bool delta_considered;
switch (lcp->deltamode) {
case LCD_Any:
delta_considered = lr->num_blocks > 0;
break;
case LCD_Increased:
delta_considered
= lr->szB > lr->old_szB
|| lr->indirect_szB > lr->old_indirect_szB
|| lr->num_blocks > lr->old_num_blocks;
break;
case LCD_Changed:
delta_considered = lr->szB != lr->old_szB
|| lr->indirect_szB != lr->old_indirect_szB
|| lr->num_blocks != lr->old_num_blocks;
break;
default:
tl_assert(0);
}
*print_record = lcp->mode == LC_Full && delta_considered
&& RiS(lr->key.state,lcp->show_leak_kinds);
// We don't count a leaks as errors with lcp->mode==LC_Summary.
// Otherwise you can get high error counts with few or no error
// messages, which can be confusing. Otherwise, we count as errors
// the leak kinds requested by --errors-for-leak-kinds=...
*count_as_error = lcp->mode == LC_Full && delta_considered
&& RiS(lr->key.state,lcp->errors_for_leak_kinds);
}
//
// Types and functions for xtree leak report.
//
static XTree* leak_xt;
/* Sizes and delta sizes for a loss record output in an xtree.
As the output format can only show positive values, we need values for
the increase and decrease cases. */
typedef
struct _XT_BIBK {
ULong szB; // Current values
ULong indirect_szB;
ULong num_blocks;
} XT_BIBK; // Bytes, Indirect bytes, BlocKs
typedef
enum {
XT_Value =0,
XT_Increase =1,
XT_Decrease =2
}
XT_VID; // Value or Increase or Decrease
typedef
struct _XT_lr {
XT_BIBK vid[3]; // indexed by XT_VID
} XT_lr;
typedef
struct _XT_Leak {
XT_lr xt_lr[4]; // indexed by Reachedness
} XT_Leak;
static void MC_(XT_Leak_init)(void* xtl)
{
VG_(memset) (xtl, 0, sizeof(XT_Leak));
}
static void MC_(XT_Leak_add) (void* to, const void* xtleak)
{
XT_Leak* xto = to;
const XT_Leak* xtl = xtleak;
for (int r = Reachable; r <= Unreached; r++)
for (int d = 0; d < 3; d++) {
xto->xt_lr[r].vid[d].szB += xtl->xt_lr[r].vid[d].szB;
xto->xt_lr[r].vid[d].indirect_szB += xtl->xt_lr[r].vid[d].indirect_szB;
xto->xt_lr[r].vid[d].num_blocks += xtl->xt_lr[r].vid[d].num_blocks;
}
}
static void XT_insert_lr (LossRecord* lr)
{
XT_Leak xtl;
Reachedness i = lr->key.state;
MC_(XT_Leak_init)(&xtl);
xtl.xt_lr[i].vid[XT_Value].szB = lr->szB;
xtl.xt_lr[i].vid[XT_Value].indirect_szB = lr->indirect_szB;
xtl.xt_lr[i].vid[XT_Value].num_blocks = lr->num_blocks;
if (lr->szB > lr->old_szB)
xtl.xt_lr[i].vid[XT_Increase].szB = lr->szB - lr->old_szB;
else
xtl.xt_lr[i].vid[XT_Decrease].szB = lr->old_szB - lr->szB;
if (lr->indirect_szB > lr->old_indirect_szB)
xtl.xt_lr[i].vid[XT_Increase].indirect_szB
= lr->indirect_szB - lr->old_indirect_szB;
else
xtl.xt_lr[i].vid[XT_Decrease].indirect_szB
= lr->old_indirect_szB - lr->indirect_szB;
if (lr->num_blocks > lr->old_num_blocks)
xtl.xt_lr[i].vid[XT_Increase].num_blocks
= lr->num_blocks - lr->old_num_blocks;
else
xtl.xt_lr[i].vid[XT_Decrease].num_blocks
= lr->old_num_blocks - lr->num_blocks;
VG_(XT_add_to_ec)(leak_xt, lr->key.allocated_at, &xtl);
}
static void MC_(XT_Leak_sub) (void* from, const void* xtleak)
{
tl_assert(0); // Should not be called.
}
static const HChar* MC_(XT_Leak_img) (const void* xtleak)
{
static XT_Leak zero;
static HChar buf[600];
UInt off = 0;
const XT_Leak* xtl = xtleak;
if (VG_(memcmp)(xtl, &zero, sizeof(XT_Leak)) != 0) {
for (UInt d = XT_Value; d <= XT_Decrease; d++) {
// print szB. We add indirect_szB to have the Unreachable showing
// the total bytes loss, including indirect loss. This is similar
// to the textual and xml reports.
for (UInt r = Reachable; r <= Unreached; r++)
off += VG_(sprintf) (buf + off, " %llu",
xtl->xt_lr[r].vid[d].szB
+ xtl->xt_lr[r].vid[d].indirect_szB);
// print indirect_szB, only for reachedness having such values)
for (UInt r = Reachable; r <= Unreached; r++)
if (r == Unreached)
off += VG_(sprintf) (buf + off, " %llu",
xtl->xt_lr[r].vid[d].indirect_szB);
// print num_blocks
for (UInt r = Reachable; r <= Unreached; r++)
off += VG_(sprintf) (buf + off, " %llu",
xtl->xt_lr[r].vid[d].num_blocks);
}
return buf + 1; // + 1 to skip the useless first space
} else {
return NULL;
}
}
/* The short event name is made of 2 or 3 or 4 letters:
an optional delta indication: i = increase d = decrease
a loss kind: R = Reachable P = Possibly I = Indirectly D = Definitely
an optional i to indicate this loss record has indirectly lost bytes
B = Bytes or Bk = Blocks.
Note that indirectly lost bytes/blocks can thus be counted in 2
loss records: the loss records for their "own" allocation stack trace,
and the loss record of the 'main' Definitely or Possibly loss record
in the indirectly lost count for these loss records. */
static const HChar* XT_Leak_events =
////// XT_Value szB
"RB : Reachable Bytes" ","
"PB : Possibly lost Bytes" ","
"IB : Indirectly lost Bytes" ","
"DB : Definitely lost Bytes (direct plus indirect)" ","
////// XT_Value indirect_szB
// no RIB
// no PIB
// no IIB
"DIB : Definitely Indirectly lost Bytes (subset of DB)" ","
////// XT_Value num_blocks
"RBk : reachable Blocks" ","
"PBk : Possibly lost Blocks" ","
"IBk : Indirectly lost Blocks" ","
"DBk : Definitely lost Blocks" ","
////// XT_Increase szB
"iRB : increase Reachable Bytes" ","
"iPB : increase Possibly lost Bytes" ","
"iIB : increase Indirectly lost Bytes" ","
"iDB : increase Definitely lost Bytes" ","
////// XT_Increase indirect_szB
// no iRIB
// no iPIB
// no iIIB
"iDIB : increase Definitely Indirectly lost Bytes" ","
////// XT_Increase num_blocks
"iRBk : increase reachable Blocks" ","
"iPBk : increase Possibly lost Blocks" ","
"iIBk : increase Indirectly lost Blocks" ","
"iDBk : increase Definitely lost Blocks" ","
////// XT_Decrease szB
"dRB : decrease Reachable Bytes" ","
"dPB : decrease Possibly lost Bytes" ","
"dIB : decrease Indirectly lost Bytes" ","
"dDB : decrease Definitely lost Bytes" ","
////// XT_Decrease indirect_szB
// no dRIB
// no dPIB
// no dIIB
"dDIB : decrease Definitely Indirectly lost Bytes" ","
////// XT_Decrease num_blocks
"dRBk : decrease reachable Blocks" ","
"dPBk : decrease Possibly lost Blocks" ","
"dIBk : decrease Indirectly lost Blocks" ","
"dDBk : decrease Definitely lost Blocks";
static void print_results(ThreadId tid, LeakCheckParams* lcp)
{
Int i, n_lossrecords, start_lr_output_scan;
LossRecord* lr;
Bool is_suppressed;
/* old_* variables are used to report delta in summary. */
SizeT old_bytes_leaked = MC_(bytes_leaked);
SizeT old_bytes_indirect = MC_(bytes_indirect);
SizeT old_bytes_dubious = MC_(bytes_dubious);
SizeT old_bytes_reachable = MC_(bytes_reachable);
SizeT old_bytes_suppressed = MC_(bytes_suppressed);
SizeT old_blocks_leaked = MC_(blocks_leaked);
SizeT old_blocks_indirect = MC_(blocks_indirect);
SizeT old_blocks_dubious = MC_(blocks_dubious);
SizeT old_blocks_reachable = MC_(blocks_reachable);
SizeT old_blocks_suppressed = MC_(blocks_suppressed);
SizeT old_bytes_heuristically_reachable[N_LEAK_CHECK_HEURISTICS];
SizeT old_blocks_heuristically_reachable[N_LEAK_CHECK_HEURISTICS];
for (i = 0; i < N_LEAK_CHECK_HEURISTICS; i++) {
old_bytes_heuristically_reachable[i]
= MC_(bytes_heuristically_reachable)[i];
MC_(bytes_heuristically_reachable)[i] = 0;
old_blocks_heuristically_reachable[i]
= MC_(blocks_heuristically_reachable)[i];
MC_(blocks_heuristically_reachable)[i] = 0;
}
if (lr_table == NULL)
// Create the lr_table, which holds the loss records.
// If the lr_table already exists, it means it contains
// loss_records from the previous leak search. The old_*
// values in these records are used to implement the
// leak check delta mode
lr_table =
VG_(OSetGen_Create)(offsetof(LossRecord, key),
cmp_LossRecordKey_LossRecord,
VG_(malloc), "mc.pr.1",
VG_(free));
// If we have loss records from a previous search, reset values to have
// proper printing of the deltas between previous search and this search.
n_lossrecords = get_lr_array_from_lr_table();
for (i = 0; i < n_lossrecords; i++) {
if (lr_array[i]->num_blocks == 0) {
// remove from lr_table the old loss_records with 0 bytes found
VG_(OSetGen_Remove) (lr_table, &lr_array[i]->key);
VG_(OSetGen_FreeNode)(lr_table, lr_array[i]);
} else {
// move the leak sizes to old_* and zero the current sizes
// for next leak search
lr_array[i]->old_szB = lr_array[i]->szB;
lr_array[i]->old_indirect_szB = lr_array[i]->indirect_szB;
lr_array[i]->old_num_blocks = lr_array[i]->num_blocks;
lr_array[i]->szB = 0;
lr_array[i]->indirect_szB = 0;
lr_array[i]->num_blocks = 0;
}
}
// lr_array now contains "invalid" loss records => free it.
// lr_array will be re-created below with the kept and new loss records.
VG_(free) (lr_array);
lr_array = NULL;
// Convert the chunks into loss records, merging them where appropriate.
for (i = 0; i < lc_n_chunks; i++) {
MC_Chunk* ch = lc_chunks[i];
LC_Extra* ex = &(lc_extras)[i];
LossRecord* old_lr;
LossRecordKey lrkey;
lrkey.state = ex->state;
lrkey.allocated_at = MC_(allocated_at)(ch);
if (ex->heuristic) {
MC_(bytes_heuristically_reachable)[ex->heuristic] += ch->szB;
MC_(blocks_heuristically_reachable)[ex->heuristic]++;
if (VG_DEBUG_LEAKCHECK)
VG_(printf)("heuristic %s %#lx len %lu\n",
pp_heuristic(ex->heuristic),
ch->data, (SizeT)ch->szB);
}
old_lr = VG_(OSetGen_Lookup)(lr_table, &lrkey);
if (old_lr) {
// We found an existing loss record matching this chunk. Update the
// loss record's details in-situ. This is safe because we don't
// change the elements used as the OSet key.
old_lr->szB += ch->szB;
if (ex->state == Unreached)
old_lr->indirect_szB += ex->IorC.indirect_szB;
old_lr->num_blocks++;
} else {
// No existing loss record matches this chunk. Create a new loss
// record, initialise it from the chunk, and insert it into lr_table.
lr = VG_(OSetGen_AllocNode)(lr_table, sizeof(LossRecord));
lr->key = lrkey;
lr->szB = ch->szB;
if (ex->state == Unreached)
lr->indirect_szB = ex->IorC.indirect_szB;
else
lr->indirect_szB = 0;
lr->num_blocks = 1;
lr->old_szB = 0;
lr->old_indirect_szB = 0;
lr->old_num_blocks = 0;
VG_(OSetGen_Insert)(lr_table, lr);
}
}
// (re-)create the array of pointers to the (new) loss records.
n_lossrecords = get_lr_array_from_lr_table ();
tl_assert(VG_(OSetGen_Size)(lr_table) == n_lossrecords);
// Sort the array by loss record sizes.
VG_(ssort)(lr_array, n_lossrecords, sizeof(LossRecord*),
cmp_LossRecords);
// Zero totals.
MC_(blocks_leaked) = MC_(bytes_leaked) = 0;
MC_(blocks_indirect) = MC_(bytes_indirect) = 0;
MC_(blocks_dubious) = MC_(bytes_dubious) = 0;
MC_(blocks_reachable) = MC_(bytes_reachable) = 0;
MC_(blocks_suppressed) = MC_(bytes_suppressed) = 0;
// If there is a maximum nr of loss records we can output, then first
// compute from where the output scan has to start.
// By default, start from the first loss record. Compute a higher
// value if there is a maximum to respect. We need to print the last
// records, as the one with the biggest sizes are more interesting.
start_lr_output_scan = 0;
if (lcp->mode == LC_Full && lcp->max_loss_records_output < n_lossrecords) {
Int nr_printable_records = 0;
for (i = n_lossrecords - 1; i >= 0 && start_lr_output_scan == 0; i--) {
Bool count_as_error, print_record;
lr = lr_array[i];
get_printing_rules (lcp, lr, &count_as_error, &print_record);
// Do not use get_printing_rules results for is_suppressed, as we
// only want to check if the record would be suppressed.
is_suppressed =
MC_(record_leak_error) ( tid, i+1, n_lossrecords, lr,
False /* print_record */,
False /* count_as_error */);
if (print_record && !is_suppressed) {
nr_printable_records++;
if (nr_printable_records == lcp->max_loss_records_output)
start_lr_output_scan = i;
}
}
}
if (lcp->xt_filename != NULL)
leak_xt = VG_(XT_create) (VG_(malloc),
"mc_leakcheck.leak_xt",
VG_(free),
sizeof(XT_Leak),
MC_(XT_Leak_init),
MC_(XT_Leak_add),
MC_(XT_Leak_sub),
VG_(XT_filter_maybe_below_main));
// Print the loss records (in size order) and collect summary stats.
for (i = start_lr_output_scan; i < n_lossrecords; i++) {
Bool count_as_error, print_record;
lr = lr_array[i];
get_printing_rules(lcp, lr, &count_as_error, &print_record);
is_suppressed =
MC_(record_leak_error)
( tid, i+1, n_lossrecords, lr,
lcp->xt_filename == NULL ? print_record : False,
count_as_error );
if (lcp->xt_filename != NULL && !is_suppressed && print_record)
XT_insert_lr (lr);
if (is_suppressed) {
MC_(blocks_suppressed) += lr->num_blocks;
MC_(bytes_suppressed) += lr->szB;
} else if (Unreached == lr->key.state) {
MC_(blocks_leaked) += lr->num_blocks;
MC_(bytes_leaked) += lr->szB;
} else if (IndirectLeak == lr->key.state) {
MC_(blocks_indirect) += lr->num_blocks;
MC_(bytes_indirect) += lr->szB;
} else if (Possible == lr->key.state) {
MC_(blocks_dubious) += lr->num_blocks;
MC_(bytes_dubious) += lr->szB;
} else if (Reachable == lr->key.state) {
MC_(blocks_reachable) += lr->num_blocks;
MC_(bytes_reachable) += lr->szB;
} else {
VG_(tool_panic)("unknown loss mode");
}
}
if (lcp->xt_filename != NULL) {
VG_(XT_callgrind_print)(leak_xt,
lcp->xt_filename,
XT_Leak_events,
MC_(XT_Leak_img));
if (VG_(clo_verbosity) >= 1 || lcp->requested_by_monitor_command)
VG_(umsg)("xtree leak report: %s\n", lcp->xt_filename);
VG_(XT_delete)(leak_xt);
}
if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
HChar d_bytes[31];
HChar d_blocks[31];
# define DBY(new,old) \
MC_(snprintf_delta) (d_bytes, sizeof(d_bytes), (new), (old), \
lcp->deltamode)
# define DBL(new,old) \
MC_(snprintf_delta) (d_blocks, sizeof(d_blocks), (new), (old), \
lcp->deltamode)
VG_(umsg)("LEAK SUMMARY:\n");
VG_(umsg)(" definitely lost: %'lu%s bytes in %'lu%s blocks\n",
MC_(bytes_leaked),
DBY (MC_(bytes_leaked), old_bytes_leaked),
MC_(blocks_leaked),
DBL (MC_(blocks_leaked), old_blocks_leaked));
VG_(umsg)(" indirectly lost: %'lu%s bytes in %'lu%s blocks\n",
MC_(bytes_indirect),
DBY (MC_(bytes_indirect), old_bytes_indirect),
MC_(blocks_indirect),
DBL (MC_(blocks_indirect), old_blocks_indirect));
VG_(umsg)(" possibly lost: %'lu%s bytes in %'lu%s blocks\n",
MC_(bytes_dubious),
DBY (MC_(bytes_dubious), old_bytes_dubious),
MC_(blocks_dubious),
DBL (MC_(blocks_dubious), old_blocks_dubious));
VG_(umsg)(" still reachable: %'lu%s bytes in %'lu%s blocks\n",
MC_(bytes_reachable),
DBY (MC_(bytes_reachable), old_bytes_reachable),
MC_(blocks_reachable),
DBL (MC_(blocks_reachable), old_blocks_reachable));
for (i = 0; i < N_LEAK_CHECK_HEURISTICS; i++)
if (old_blocks_heuristically_reachable[i] > 0
|| MC_(blocks_heuristically_reachable)[i] > 0) {
VG_(umsg)(" of which "
"reachable via heuristic:\n");
break;
}
for (i = 0; i < N_LEAK_CHECK_HEURISTICS; i++)
if (old_blocks_heuristically_reachable[i] > 0
|| MC_(blocks_heuristically_reachable)[i] > 0)
VG_(umsg)(" %-19s: "
"%'lu%s bytes in %'lu%s blocks\n",
pp_heuristic(i),
MC_(bytes_heuristically_reachable)[i],
DBY (MC_(bytes_heuristically_reachable)[i],
old_bytes_heuristically_reachable[i]),
MC_(blocks_heuristically_reachable)[i],
DBL (MC_(blocks_heuristically_reachable)[i],
old_blocks_heuristically_reachable[i]));
VG_(umsg)(" suppressed: %'lu%s bytes in %'lu%s blocks\n",
MC_(bytes_suppressed),
DBY (MC_(bytes_suppressed), old_bytes_suppressed),
MC_(blocks_suppressed),
DBL (MC_(blocks_suppressed), old_blocks_suppressed));
if (lcp->mode != LC_Full &&
(MC_(blocks_leaked) + MC_(blocks_indirect) +
MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
if (lcp->requested_by_monitor_command)
VG_(umsg)("To see details of leaked memory, "
"give 'full' arg to leak_check\n");
else
VG_(umsg)("Rerun with --leak-check=full to see details "
"of leaked memory\n");
}
if (lcp->mode == LC_Full &&
MC_(blocks_reachable) > 0 && !RiS(Reachable,lcp->show_leak_kinds)) {
VG_(umsg)("Reachable blocks (those to which a pointer "
"was found) are not shown.\n");
if (lcp->requested_by_monitor_command)
VG_(umsg)("To see them, add 'reachable any' args to leak_check\n");
else
VG_(umsg)("To see them, rerun with: --leak-check=full "
"--show-leak-kinds=all\n");
}
VG_(umsg)("\n");
#undef DBL
#undef DBY
}
}
// print recursively all indirectly leaked blocks collected in clique.
// Printing stops when *remaining reaches 0.
static void print_clique (Int clique, UInt level, UInt *remaining)
{
Int ind;
UInt i, n_lossrecords;
n_lossrecords = VG_(OSetGen_Size)(lr_table);
for (ind = 0; ind < lc_n_chunks && *remaining > 0; ind++) {
LC_Extra* ind_ex = &(lc_extras)[ind];
if (ind_ex->state == IndirectLeak
&& ind_ex->IorC.clique == (SizeT) clique) {
MC_Chunk* ind_ch = lc_chunks[ind];
LossRecord* ind_lr;
LossRecordKey ind_lrkey;
UInt lr_i;
ind_lrkey.state = ind_ex->state;
ind_lrkey.allocated_at = MC_(allocated_at)(ind_ch);
ind_lr = VG_(OSetGen_Lookup)(lr_table, &ind_lrkey);
for (lr_i = 0; lr_i < n_lossrecords; lr_i++)
if (ind_lr == lr_array[lr_i])
break;
for (i = 0; i < level; i++)
VG_(umsg)(" ");
VG_(umsg)("%p[%lu] indirect loss record %u\n",
(void *)ind_ch->data, (SizeT)ind_ch->szB,
lr_i+1); // lr_i+1 for user numbering.
(*remaining)--;
if (lr_i >= n_lossrecords)
VG_(umsg)
("error: no indirect loss record found for %p[%lu]?????\n",
(void *)ind_ch->data, (SizeT)ind_ch->szB);
print_clique(ind, level+1, remaining);
}
}
}
Bool MC_(print_block_list) ( UInt loss_record_nr_from,
UInt loss_record_nr_to,
UInt max_blocks,
UInt heuristics)
{
UInt loss_record_nr;
UInt i, n_lossrecords;
LossRecord* lr;
Bool lr_printed;
UInt remaining = max_blocks;
if (lr_table == NULL || lc_chunks == NULL || lc_extras == NULL) {
VG_(umsg)("Can't print block list : no valid leak search result\n");
return False;
}
if (lc_chunks_n_frees_marker != MC_(get_cmalloc_n_frees)()) {
VG_(umsg)("Can't print obsolete block list : redo a leak search first\n");
return False;
}
n_lossrecords = VG_(OSetGen_Size)(lr_table);
if (loss_record_nr_from >= n_lossrecords)
return False; // Invalid starting loss record nr.
if (loss_record_nr_to >= n_lossrecords)
loss_record_nr_to = n_lossrecords - 1;
tl_assert (lr_array);
for (loss_record_nr = loss_record_nr_from;
loss_record_nr <= loss_record_nr_to && remaining > 0;
loss_record_nr++) {
lr = lr_array[loss_record_nr];
lr_printed = False;
/* If user asks to print a specific loss record, we print
the block details, even if no block will be shown for this lr.
If user asks to print a range of lr, we only print lr details
when at least one block is shown. */
if (loss_record_nr_from == loss_record_nr_to) {
/* (+1 on loss_record_nr as user numbering for loss records
starts at 1). */
MC_(pp_LossRecord)(loss_record_nr+1, n_lossrecords, lr);
lr_printed = True;
}
// Match the chunks with loss records.
for (i = 0; i < lc_n_chunks && remaining > 0; i++) {
MC_Chunk* ch = lc_chunks[i];
LC_Extra* ex = &(lc_extras)[i];
LossRecord* old_lr;
LossRecordKey lrkey;
lrkey.state = ex->state;
lrkey.allocated_at = MC_(allocated_at)(ch);
old_lr = VG_(OSetGen_Lookup)(lr_table, &lrkey);
if (old_lr) {
// We found an existing loss record matching this chunk.
// If this is the loss record we are looking for, output the
// pointer.
if (old_lr == lr_array[loss_record_nr]
&& (heuristics == 0 || HiS(ex->heuristic, heuristics))) {
if (!lr_printed) {
MC_(pp_LossRecord)(loss_record_nr+1, n_lossrecords, lr);
lr_printed = True;
}
if (ex->heuristic)
VG_(umsg)("%p[%lu] (found via heuristic %s)\n",
(void *)ch->data, (SizeT)ch->szB,
pp_heuristic (ex->heuristic));
else
VG_(umsg)("%p[%lu]\n",
(void *)ch->data, (SizeT)ch->szB);
remaining--;
if (ex->state != Reachable) {
// We can print the clique in all states, except Reachable.
// In Unreached state, lc_chunk[i] is the clique leader.
// In IndirectLeak, lc_chunk[i] might have been a clique
// leader which was later collected in another clique.
// For Possible, lc_chunk[i] might be the top of a clique
// or an intermediate clique.
print_clique(i, 1, &remaining);
}
}
} else {
// No existing loss record matches this chunk ???
VG_(umsg)("error: no loss record found for %p[%lu]?????\n",
(void *)ch->data, (SizeT)ch->szB);
}
}
}
return True;
}
// If searched = 0, scan memory root set, pushing onto the mark stack the blocks
// encountered.
// Otherwise (searched != 0), scan the memory root set searching for ptr
// pointing inside [searched, searched+szB[.
static void scan_memory_root_set(Addr searched, SizeT szB)
{
Int i;
Int n_seg_starts;
Addr* seg_starts = VG_(get_segment_starts)( SkFileC | SkAnonC | SkShmC,
&n_seg_starts );
tl_assert(seg_starts && n_seg_starts > 0);
lc_scanned_szB = 0;
lc_sig_skipped_szB = 0;
// VG_(am_show_nsegments)( 0, "leakcheck");
for (i = 0; i < n_seg_starts; i++) {
SizeT seg_size;
NSegment const* seg = VG_(am_find_nsegment)( seg_starts[i] );
tl_assert(seg);
tl_assert(seg->kind == SkFileC || seg->kind == SkAnonC ||
seg->kind == SkShmC);
if (!(seg->hasR && seg->hasW)) continue;
if (seg->isCH) continue;
// Don't poke around in device segments as this may cause
// hangs. Include /dev/zero just in case someone allocated
// memory by explicitly mapping /dev/zero.
if (seg->kind == SkFileC
&& (VKI_S_ISCHR(seg->mode) || VKI_S_ISBLK(seg->mode))) {
const HChar* dev_name = VG_(am_get_filename)( seg );
if (dev_name && 0 == VG_(strcmp)(dev_name, "/dev/zero")) {
// Don't skip /dev/zero.
} else {
// Skip this device mapping.
continue;
}
}
if (0)
VG_(printf)("ACCEPT %2d %#lx %#lx\n", i, seg->start, seg->end);
// Scan the segment. We use -1 for the clique number, because this
// is a root-set.
seg_size = seg->end - seg->start + 1;
if (VG_(clo_verbosity) > 2) {
VG_(message)(Vg_DebugMsg,
" Scanning root segment: %#lx..%#lx (%lu)\n",
seg->start, seg->end, seg_size);
}
lc_scan_memory(seg->start, seg_size, /*is_prior_definite*/True,
/*clique*/-1, /*cur_clique*/-1,
searched, szB);
}
VG_(free)(seg_starts);
}
static MC_Mempool *find_mp_of_chunk (MC_Chunk* mc_search)
{
MC_Mempool* mp;
tl_assert( MC_(mempool_list) );
VG_(HT_ResetIter)( MC_(mempool_list) );
while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
MC_Chunk* mc;
VG_(HT_ResetIter)(mp->chunks);
while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
if (mc == mc_search)
return mp;
}
}
return NULL;
}
/*------------------------------------------------------------*/
/*--- Top-level entry point. ---*/
/*------------------------------------------------------------*/
void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams* lcp)
{
Int i, j;
tl_assert(lcp->mode != LC_Off);
// Verify some assertions which are used in lc_scan_memory.
tl_assert((VKI_PAGE_SIZE % sizeof(Addr)) == 0);
tl_assert((SM_SIZE % sizeof(Addr)) == 0);
// Above two assertions are critical, while below assertion
// ensures that the optimisation in the loop is done in the
// correct order : the loop checks for (big) SM chunk skipping
// before checking for (smaller) page skipping.
tl_assert((SM_SIZE % VKI_PAGE_SIZE) == 0);
MC_(leak_search_gen)++;
MC_(detect_memory_leaks_last_delta_mode) = lcp->deltamode;
detect_memory_leaks_last_heuristics = lcp->heuristics;
// Get the chunks, stop if there were none.
if (lc_chunks) {
VG_(free)(lc_chunks);
lc_chunks = NULL;
}
lc_chunks = find_active_chunks(&lc_n_chunks);
lc_chunks_n_frees_marker = MC_(get_cmalloc_n_frees)();
if (lc_n_chunks == 0) {
tl_assert(lc_chunks == NULL);
if (lr_table != NULL) {
// forget the previous recorded LossRecords as next leak search
// can in any case just create new leaks.
// Maybe it would be better to rather call print_result ?
// (at least when leak decreases are requested)
// This will then output all LossRecords with a size decreasing to 0
VG_(OSetGen_Destroy) (lr_table);
lr_table = NULL;
}
if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
VG_(umsg)("All heap blocks were freed -- no leaks are possible\n");
VG_(umsg)("\n");
}
return;
}
// Sort the array so blocks are in ascending order in memory.
VG_(ssort)(lc_chunks, lc_n_chunks, sizeof(VgHashNode*), compare_MC_Chunks);
// Sanity check -- make sure they're in order.
for (i = 0; i < lc_n_chunks-1; i++) {
tl_assert( lc_chunks[i]->data <= lc_chunks[i+1]->data);
}
// Sanity check -- make sure they don't overlap. One exception is that
// we allow a MALLOCLIKE block to sit entirely within a malloc() block.
// This is for bug 100628. If this occurs, we ignore the malloc() block
// for leak-checking purposes. This is a hack and probably should be done
// better, but at least it's consistent with mempools (which are treated
// like this in find_active_chunks). Mempools have a separate VgHashTable
// for mempool chunks, but if custom-allocated blocks are put in a separate
// table from normal heap blocks it makes free-mismatch checking more
// difficult.
// Another exception: Metapool memory blocks overlap by definition. The meta-
// block is allocated (by a custom allocator), and chunks of that block are
// allocated again for use by the application: Not an error.
//
// If this check fails, it probably means that the application
// has done something stupid with VALGRIND_MALLOCLIKE_BLOCK client
// requests, eg. has made overlapping requests (which are
// nonsensical), or used VALGRIND_MALLOCLIKE_BLOCK for stack locations;
// again nonsensical.
//
for (i = 0; i < lc_n_chunks-1; i++) {
MC_Chunk* ch1 = lc_chunks[i];
MC_Chunk* ch2 = lc_chunks[i+1];
Addr start1 = ch1->data;
Addr start2 = ch2->data;
Addr end1 = ch1->data + ch1->szB - 1;
Addr end2 = ch2->data + ch2->szB - 1;
Bool isCustom1 = ch1->allockind == MC_AllocCustom;
Bool isCustom2 = ch2->allockind == MC_AllocCustom;
if (end1 < start2) {
// Normal case - no overlap.
// We used to allow exact duplicates, I'm not sure why. --njn
//} else if (start1 == start2 && end1 == end2) {
// Degenerate case: exact duplicates.
} else if (start1 >= start2 && end1 <= end2 && isCustom1 && !isCustom2) {
// Block i is MALLOCLIKE and entirely within block i+1.
// Remove block i+1.
for (j = i+1; j < lc_n_chunks-1; j++) {
lc_chunks[j] = lc_chunks[j+1];
}
lc_n_chunks--;
} else if (start2 >= start1 && end2 <= end1 && isCustom2 && !isCustom1) {
// Block i+1 is MALLOCLIKE and entirely within block i.
// Remove block i.
for (j = i; j < lc_n_chunks-1; j++) {
lc_chunks[j] = lc_chunks[j+1];
}
lc_n_chunks--;
} else {
// Overlap is allowed ONLY when one of the two candicates is a block
// from a memory pool that has the metapool attribute set.
// All other mixtures trigger the error + assert.
MC_Mempool* mp;
Bool ch1_is_meta = False, ch2_is_meta = False;
Bool Inappropriate = False;
if (MC_(is_mempool_block)(ch1)) {
mp = find_mp_of_chunk(ch1);
if (mp && mp->metapool) {
ch1_is_meta = True;
}
}
if (MC_(is_mempool_block)(ch2)) {
mp = find_mp_of_chunk(ch2);
if (mp && mp->metapool) {
ch2_is_meta = True;
}
}
// If one of the blocks is a meta block, the other must be entirely
// within that meta block, or something is really wrong with the custom
// allocator.
if (ch1_is_meta != ch2_is_meta) {
if ( (ch1_is_meta && (start2 < start1 || end2 > end1)) ||
(ch2_is_meta && (start1 < start2 || end1 > end2)) ) {
Inappropriate = True;
}
}
if (ch1_is_meta == ch2_is_meta || Inappropriate) {
VG_(umsg)("Block 0x%lx..0x%lx overlaps with block 0x%lx..0x%lx\n",
start1, end1, start2, end2);
VG_(umsg)("Blocks allocation contexts:\n"),
VG_(pp_ExeContext)( MC_(allocated_at)(ch1));
VG_(umsg)("\n"),
VG_(pp_ExeContext)( MC_(allocated_at)(ch2));
VG_(umsg)("This is usually caused by using ");
VG_(umsg)("VALGRIND_MALLOCLIKE_BLOCK in an inappropriate way.\n");
tl_assert (0);
}
}
}
// Initialise lc_extras.
if (lc_extras) {
VG_(free)(lc_extras);
lc_extras = NULL;
}
lc_extras = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(LC_Extra) );
for (i = 0; i < lc_n_chunks; i++) {
lc_extras[i].state = Unreached;
lc_extras[i].pending = False;
lc_extras[i].heuristic = LchNone;
lc_extras[i].IorC.indirect_szB = 0;
}
// Initialise lc_markstack.
lc_markstack = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(Int) );
for (i = 0; i < lc_n_chunks; i++) {
lc_markstack[i] = -1;
}
lc_markstack_top = -1;
// Verbosity.
if (VG_(clo_verbosity) > 1 && !VG_(clo_xml)) {
VG_(umsg)( "Searching for pointers to %'d not-freed blocks\n",
lc_n_chunks );
}
// Scan the memory root-set, pushing onto the mark stack any blocks
// pointed to.
scan_memory_root_set(/*searched*/0, 0);
// Scan GP registers for chunk pointers.
VG_(apply_to_GP_regs)(lc_push_if_a_chunk_ptr_register);
// Process the pushed blocks. After this, every block that is reachable
// from the root-set has been traced.
lc_process_markstack(/*clique*/-1);
if (VG_(clo_verbosity) > 1 && !VG_(clo_xml)) {
VG_(umsg)("Checked %'lu bytes\n", lc_scanned_szB);
if (lc_sig_skipped_szB > 0)
VG_(umsg)("Skipped %'lu bytes due to read errors\n",
lc_sig_skipped_szB);
VG_(umsg)( "\n" );
}
// Trace all the leaked blocks to determine which are directly leaked and
// which are indirectly leaked. For each Unreached block, push it onto
// the mark stack, and find all the as-yet-Unreached blocks reachable
// from it. These form a clique and are marked IndirectLeak, and their
// size is added to the clique leader's indirect size. If one of the
// found blocks was itself a clique leader (from a previous clique), then
// the cliques are merged.
for (i = 0; i < lc_n_chunks; i++) {
MC_Chunk* ch = lc_chunks[i];
LC_Extra* ex = &(lc_extras[i]);
if (VG_DEBUG_CLIQUE)
VG_(printf)("cliques: %d at %#lx -> Loss state %d\n",
i, ch->data, ex->state);
tl_assert(lc_markstack_top == -1);
if (ex->state == Unreached) {
if (VG_DEBUG_CLIQUE)
VG_(printf)("%d: gathering clique %#lx\n", i, ch->data);
// Push this Unreached block onto the stack and process it.
lc_push(i, ch);
lc_process_markstack(/*clique*/i);
tl_assert(lc_markstack_top == -1);
tl_assert(ex->state == Unreached);
}
}
print_results( tid, lcp);
VG_(free) ( lc_markstack );
lc_markstack = NULL;
// lc_chunks, lc_extras, lr_array and lr_table are kept (needed if user
// calls MC_(print_block_list)). lr_table also used for delta leak reporting
// between this leak search and the next leak search.
}
static Addr searched_wpa;
static SizeT searched_szB;
static void
search_address_in_GP_reg(ThreadId tid, const HChar* regname, Addr addr_in_reg)
{
if (addr_in_reg >= searched_wpa
&& addr_in_reg < searched_wpa + searched_szB) {
if (addr_in_reg == searched_wpa)
VG_(umsg)
("tid %u register %s pointing at %#lx\n",
tid, regname, searched_wpa);
else
VG_(umsg)
("tid %u register %s interior pointing %lu bytes inside %#lx\n",
tid, regname, (long unsigned) addr_in_reg - searched_wpa,
searched_wpa);
}
}
void MC_(who_points_at) ( Addr address, SizeT szB)
{
MC_Chunk** chunks;
Int n_chunks;
Int i;
if (szB == 1)
VG_(umsg) ("Searching for pointers to %#lx\n", address);
else
VG_(umsg) ("Searching for pointers pointing in %lu bytes from %#lx\n",
szB, address);
chunks = find_active_chunks(&n_chunks);
// Scan memory root-set, searching for ptr pointing in address[szB]
scan_memory_root_set(address, szB);
// Scan active malloc-ed chunks
for (i = 0; i < n_chunks; i++) {
lc_scan_memory(chunks[i]->data, chunks[i]->szB,
/*is_prior_definite*/True,
/*clique*/-1, /*cur_clique*/-1,
address, szB);
}
VG_(free) ( chunks );
// Scan GP registers for pointers to address range.
searched_wpa = address;
searched_szB = szB;
VG_(apply_to_GP_regs)(search_address_in_GP_reg);
}
/*--------------------------------------------------------------------*/
/*--- end ---*/
/*--------------------------------------------------------------------*/
|