kernel/vm: Move most kernel debugger routines to a new vm_debug.cpp.
Reduces the size of vm.cpp by over 800 lines. No behavioral change intended.
This commit is contained in:
@@ -144,6 +144,7 @@ status_t vm_put_physical_page_debug(addr_t vaddr, void* handle);
|
||||
void vm_get_info(system_info *info);
|
||||
uint32 vm_num_page_faults(void);
|
||||
off_t vm_available_memory(void);
|
||||
off_t vm_available_memory_debug(void);
|
||||
off_t vm_available_not_needed_memory(void);
|
||||
off_t vm_available_not_needed_memory_debug(void);
|
||||
size_t vm_kernel_address_space_left(void);
|
||||
|
||||
@@ -31,6 +31,8 @@ status_t vm_page_fault(addr_t address, addr_t faultAddress, bool isWrite,
|
||||
bool isExecute, bool isUser, addr_t *newip);
|
||||
void vm_unreserve_memory(size_t bytes);
|
||||
status_t vm_try_reserve_memory(size_t bytes, int priority, bigtime_t timeout);
|
||||
|
||||
void vm_debug_init();
|
||||
status_t vm_daemon_init(void);
|
||||
|
||||
const char *page_state_to_string(int state);
|
||||
|
||||
@@ -9,6 +9,7 @@ UsePrivateHeaders [ FDirName kernel util ] ;
|
||||
KernelMergeObject kernel_vm.o :
|
||||
PageCacheLocker.cpp
|
||||
vm.cpp
|
||||
vm_debug.cpp
|
||||
vm_page.cpp
|
||||
VMAddressSpace.cpp
|
||||
VMAddressSpaceLocking.cpp
|
||||
|
||||
+11
-864
@@ -256,19 +256,6 @@ static uint32 sPageFaults;
|
||||
|
||||
static VMPhysicalPageMapper* sPhysicalPageMapper;
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
|
||||
struct cache_info {
|
||||
VMCache* cache;
|
||||
addr_t page_count;
|
||||
addr_t committed;
|
||||
};
|
||||
|
||||
static const uint32 kCacheInfoTableCount = 100 * 1024;
|
||||
static cache_info* sCacheInfoTable;
|
||||
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
|
||||
// function declarations
|
||||
static void delete_area(VMAddressSpace* addressSpace, VMArea* area,
|
||||
@@ -3292,717 +3279,6 @@ vm_remove_all_page_mappings_if_unaccessed(struct vm_page *page)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
display_mem(int argc, char** argv)
|
||||
{
|
||||
bool physical = false;
|
||||
addr_t copyAddress;
|
||||
int32 displayWidth;
|
||||
int32 itemSize;
|
||||
int32 num = -1;
|
||||
addr_t address;
|
||||
int i = 1, j;
|
||||
|
||||
if (argc > 1 && argv[1][0] == '-') {
|
||||
if (!strcmp(argv[1], "-p") || !strcmp(argv[1], "--physical")) {
|
||||
physical = true;
|
||||
i++;
|
||||
} else
|
||||
i = 99;
|
||||
}
|
||||
|
||||
if (argc < i + 1 || argc > i + 2) {
|
||||
kprintf("usage: dl/dw/ds/db/string [-p|--physical] <address> [num]\n"
|
||||
"\tdl - 8 bytes\n"
|
||||
"\tdw - 4 bytes\n"
|
||||
"\tds - 2 bytes\n"
|
||||
"\tdb - 1 byte\n"
|
||||
"\tstring - a whole string\n"
|
||||
" -p or --physical only allows memory from a single page to be "
|
||||
"displayed.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
address = parse_expression(argv[i]);
|
||||
|
||||
if (argc > i + 1)
|
||||
num = parse_expression(argv[i + 1]);
|
||||
|
||||
// build the format string
|
||||
if (strcmp(argv[0], "db") == 0) {
|
||||
itemSize = 1;
|
||||
displayWidth = 16;
|
||||
} else if (strcmp(argv[0], "ds") == 0) {
|
||||
itemSize = 2;
|
||||
displayWidth = 8;
|
||||
} else if (strcmp(argv[0], "dw") == 0) {
|
||||
itemSize = 4;
|
||||
displayWidth = 4;
|
||||
} else if (strcmp(argv[0], "dl") == 0) {
|
||||
itemSize = 8;
|
||||
displayWidth = 2;
|
||||
} else if (strcmp(argv[0], "string") == 0) {
|
||||
itemSize = 1;
|
||||
displayWidth = -1;
|
||||
} else {
|
||||
kprintf("display_mem called in an invalid way!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (num <= 0)
|
||||
num = displayWidth;
|
||||
|
||||
void* physicalPageHandle = NULL;
|
||||
|
||||
if (physical) {
|
||||
int32 offset = address & (B_PAGE_SIZE - 1);
|
||||
if (num * itemSize + offset > B_PAGE_SIZE) {
|
||||
num = (B_PAGE_SIZE - offset) / itemSize;
|
||||
kprintf("NOTE: number of bytes has been cut to page size\n");
|
||||
}
|
||||
|
||||
address = ROUNDDOWN(address, B_PAGE_SIZE);
|
||||
|
||||
if (vm_get_physical_page_debug(address, ©Address,
|
||||
&physicalPageHandle) != B_OK) {
|
||||
kprintf("getting the hardware page failed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
address += offset;
|
||||
copyAddress += offset;
|
||||
} else
|
||||
copyAddress = address;
|
||||
|
||||
if (!strcmp(argv[0], "string")) {
|
||||
kprintf("%p \"", (char*)copyAddress);
|
||||
|
||||
// string mode
|
||||
for (i = 0; true; i++) {
|
||||
char c;
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &c, (char*)copyAddress + i, 1)
|
||||
!= B_OK
|
||||
|| c == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
kprintf("\\n");
|
||||
else if (c == '\t')
|
||||
kprintf("\\t");
|
||||
else {
|
||||
if (!isprint(c))
|
||||
c = '.';
|
||||
|
||||
kprintf("%c", c);
|
||||
}
|
||||
}
|
||||
|
||||
kprintf("\"\n");
|
||||
} else {
|
||||
// number mode
|
||||
for (i = 0; i < num; i++) {
|
||||
uint64 value;
|
||||
|
||||
if ((i % displayWidth) == 0) {
|
||||
int32 displayed = min_c(displayWidth, (num-i)) * itemSize;
|
||||
if (i != 0)
|
||||
kprintf("\n");
|
||||
|
||||
kprintf("[0x%lx] ", address + i * itemSize);
|
||||
|
||||
for (j = 0; j < displayed; j++) {
|
||||
char c;
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &c,
|
||||
(char*)copyAddress + i * itemSize + j, 1) != B_OK) {
|
||||
displayed = j;
|
||||
break;
|
||||
}
|
||||
if (!isprint(c))
|
||||
c = '.';
|
||||
|
||||
kprintf("%c", c);
|
||||
}
|
||||
if (num > displayWidth) {
|
||||
// make sure the spacing in the last line is correct
|
||||
for (j = displayed; j < displayWidth * itemSize; j++)
|
||||
kprintf(" ");
|
||||
}
|
||||
kprintf(" ");
|
||||
}
|
||||
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &value,
|
||||
(uint8*)copyAddress + i * itemSize, itemSize) != B_OK) {
|
||||
kprintf("read fault");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (itemSize) {
|
||||
case 1:
|
||||
kprintf(" %02" B_PRIx8, *(uint8*)&value);
|
||||
break;
|
||||
case 2:
|
||||
kprintf(" %04" B_PRIx16, *(uint16*)&value);
|
||||
break;
|
||||
case 4:
|
||||
kprintf(" %08" B_PRIx32, *(uint32*)&value);
|
||||
break;
|
||||
case 8:
|
||||
kprintf(" %016" B_PRIx64, *(uint64*)&value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kprintf("\n");
|
||||
}
|
||||
|
||||
if (physical) {
|
||||
copyAddress = ROUNDDOWN(copyAddress, B_PAGE_SIZE);
|
||||
vm_put_physical_page_debug(copyAddress, physicalPageHandle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_cache_tree_recursively(VMCache* cache, int level,
|
||||
VMCache* highlightCache)
|
||||
{
|
||||
// print this cache
|
||||
for (int i = 0; i < level; i++)
|
||||
kprintf(" ");
|
||||
if (cache == highlightCache)
|
||||
kprintf("%p <--\n", cache);
|
||||
else
|
||||
kprintf("%p\n", cache);
|
||||
|
||||
// recursively print its consumers
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
dump_cache_tree_recursively(consumer, level + 1, highlightCache);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_cache_tree(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: %s <address>\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t address = parse_expression(argv[1]);
|
||||
if (address == 0)
|
||||
return 0;
|
||||
|
||||
VMCache* cache = (VMCache*)address;
|
||||
VMCache* root = cache;
|
||||
|
||||
// find the root cache (the transitive source)
|
||||
while (root->source != NULL)
|
||||
root = root->source;
|
||||
|
||||
dump_cache_tree_recursively(root, 0, cache);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
vm_cache_type_to_string(int32 type)
|
||||
{
|
||||
switch (type) {
|
||||
case CACHE_TYPE_RAM:
|
||||
return "RAM";
|
||||
case CACHE_TYPE_DEVICE:
|
||||
return "device";
|
||||
case CACHE_TYPE_VNODE:
|
||||
return "vnode";
|
||||
case CACHE_TYPE_NULL:
|
||||
return "null";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
|
||||
static void
|
||||
update_cache_info_recursively(VMCache* cache, cache_info& info)
|
||||
{
|
||||
info.page_count += cache->page_count;
|
||||
if (cache->type == CACHE_TYPE_RAM)
|
||||
info.committed += cache->committed_size;
|
||||
|
||||
// recurse
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
update_cache_info_recursively(consumer, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cache_info_compare_page_count(const void* _a, const void* _b)
|
||||
{
|
||||
const cache_info* a = (const cache_info*)_a;
|
||||
const cache_info* b = (const cache_info*)_b;
|
||||
if (a->page_count == b->page_count)
|
||||
return 0;
|
||||
return a->page_count < b->page_count ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cache_info_compare_committed(const void* _a, const void* _b)
|
||||
{
|
||||
const cache_info* a = (const cache_info*)_a;
|
||||
const cache_info* b = (const cache_info*)_b;
|
||||
if (a->committed == b->committed)
|
||||
return 0;
|
||||
return a->committed < b->committed ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_caches_recursively(VMCache* cache, cache_info& info, int level)
|
||||
{
|
||||
for (int i = 0; i < level; i++)
|
||||
kprintf(" ");
|
||||
|
||||
kprintf("%p: type: %s, base: %" B_PRIdOFF ", size: %" B_PRIdOFF ", "
|
||||
"pages: %" B_PRIu32, cache, vm_cache_type_to_string(cache->type),
|
||||
cache->virtual_base, cache->virtual_end, cache->page_count);
|
||||
|
||||
if (level == 0)
|
||||
kprintf("/%lu", info.page_count);
|
||||
|
||||
if (cache->type == CACHE_TYPE_RAM || (level == 0 && info.committed > 0)) {
|
||||
kprintf(", committed: %" B_PRIdOFF, cache->committed_size);
|
||||
|
||||
if (level == 0)
|
||||
kprintf("/%lu", info.committed);
|
||||
}
|
||||
|
||||
// areas
|
||||
if (cache->areas != NULL) {
|
||||
VMArea* area = cache->areas;
|
||||
kprintf(", areas: %" B_PRId32 " (%s, team: %" B_PRId32 ")", area->id,
|
||||
area->name, area->address_space->ID());
|
||||
|
||||
while (area->cache_next != NULL) {
|
||||
area = area->cache_next;
|
||||
kprintf(", %" B_PRId32, area->id);
|
||||
}
|
||||
}
|
||||
|
||||
kputs("\n");
|
||||
|
||||
// recurse
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
dump_caches_recursively(consumer, info, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_caches(int argc, char** argv)
|
||||
{
|
||||
if (sCacheInfoTable == NULL) {
|
||||
kprintf("No cache info table!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool sortByPageCount = true;
|
||||
|
||||
for (int32 i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-c") == 0) {
|
||||
sortByPageCount = false;
|
||||
} else {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 totalCount = 0;
|
||||
uint32 rootCount = 0;
|
||||
off_t totalCommitted = 0;
|
||||
page_num_t totalPages = 0;
|
||||
|
||||
VMCache* cache = gDebugCacheList;
|
||||
while (cache) {
|
||||
totalCount++;
|
||||
if (cache->source == NULL) {
|
||||
cache_info stackInfo;
|
||||
cache_info& info = rootCount < kCacheInfoTableCount
|
||||
? sCacheInfoTable[rootCount] : stackInfo;
|
||||
rootCount++;
|
||||
info.cache = cache;
|
||||
info.page_count = 0;
|
||||
info.committed = 0;
|
||||
update_cache_info_recursively(cache, info);
|
||||
totalCommitted += info.committed;
|
||||
totalPages += info.page_count;
|
||||
}
|
||||
|
||||
cache = cache->debug_next;
|
||||
}
|
||||
|
||||
kprintf("total committed memory: %" B_PRIdOFF ", total used pages: %"
|
||||
B_PRIuPHYSADDR "\n", totalCommitted, totalPages);
|
||||
kprintf("%" B_PRIu32 " caches (%" B_PRIu32 " root caches), sorted by %s "
|
||||
"per cache tree...\n\n", totalCount, rootCount, sortByPageCount ?
|
||||
"page count" : "committed size");
|
||||
|
||||
if (rootCount > kCacheInfoTableCount) {
|
||||
kprintf("Cache info table too small! Can't sort and print caches!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
qsort(sCacheInfoTable, rootCount, sizeof(cache_info),
|
||||
sortByPageCount
|
||||
? &cache_info_compare_page_count
|
||||
: &cache_info_compare_committed);
|
||||
|
||||
for (uint32 i = 0; i < rootCount; i++) {
|
||||
cache_info& info = sCacheInfoTable[i];
|
||||
dump_caches_recursively(info.cache, info, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
|
||||
static int
|
||||
dump_cache(int argc, char** argv)
|
||||
{
|
||||
VMCache* cache;
|
||||
bool showPages = false;
|
||||
int i = 1;
|
||||
|
||||
if (argc < 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: %s [-ps] <address>\n"
|
||||
" if -p is specified, all pages are shown, if -s is used\n"
|
||||
" only the cache info is shown respectively.\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
while (argv[i][0] == '-') {
|
||||
char* arg = argv[i] + 1;
|
||||
while (arg[0]) {
|
||||
if (arg[0] == 'p')
|
||||
showPages = true;
|
||||
arg++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (argv[i] == NULL) {
|
||||
kprintf("%s: invalid argument, pass address\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t address = parse_expression(argv[i]);
|
||||
if (address == 0)
|
||||
return 0;
|
||||
|
||||
cache = (VMCache*)address;
|
||||
|
||||
cache->Dump(showPages);
|
||||
|
||||
set_debug_variable("_sourceCache", (addr_t)cache->source);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_area_struct(VMArea* area, bool mappings)
|
||||
{
|
||||
kprintf("AREA: %p\n", area);
|
||||
kprintf("name:\t\t'%s'\n", area->name);
|
||||
kprintf("owner:\t\t0x%" B_PRIx32 "\n", area->address_space->ID());
|
||||
kprintf("id:\t\t0x%" B_PRIx32 "\n", area->id);
|
||||
kprintf("base:\t\t0x%lx\n", area->Base());
|
||||
kprintf("size:\t\t0x%lx\n", area->Size());
|
||||
kprintf("protection:\t0x%" B_PRIx32 "\n", area->protection);
|
||||
kprintf("page_protection:%p\n", area->page_protections);
|
||||
kprintf("wiring:\t\t0x%x\n", area->wiring);
|
||||
kprintf("memory_type:\t%#" B_PRIx32 "\n", area->MemoryType());
|
||||
kprintf("cache:\t\t%p\n", area->cache);
|
||||
kprintf("cache_type:\t%s\n", vm_cache_type_to_string(area->cache_type));
|
||||
kprintf("cache_offset:\t0x%" B_PRIx64 "\n", area->cache_offset);
|
||||
kprintf("cache_next:\t%p\n", area->cache_next);
|
||||
kprintf("cache_prev:\t%p\n", area->cache_prev);
|
||||
|
||||
VMAreaMappings::Iterator iterator = area->mappings.GetIterator();
|
||||
if (mappings) {
|
||||
kprintf("page mappings:\n");
|
||||
while (iterator.HasNext()) {
|
||||
vm_page_mapping* mapping = iterator.Next();
|
||||
kprintf(" %p", mapping->page);
|
||||
}
|
||||
kprintf("\n");
|
||||
} else {
|
||||
uint32 count = 0;
|
||||
while (iterator.Next() != NULL) {
|
||||
count++;
|
||||
}
|
||||
kprintf("page mappings:\t%" B_PRIu32 "\n", count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_area(int argc, char** argv)
|
||||
{
|
||||
bool mappings = false;
|
||||
bool found = false;
|
||||
int32 index = 1;
|
||||
VMArea* area;
|
||||
addr_t num;
|
||||
|
||||
if (argc < 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: area [-m] [id|contains|address|name] <id|address|name>\n"
|
||||
"All areas matching either id/address/name are listed. You can\n"
|
||||
"force to check only a specific item by prefixing the specifier\n"
|
||||
"with the id/contains/address/name keywords.\n"
|
||||
"-m shows the area's mappings as well.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "-m")) {
|
||||
mappings = true;
|
||||
index++;
|
||||
}
|
||||
|
||||
int32 mode = 0xf;
|
||||
if (!strcmp(argv[index], "id"))
|
||||
mode = 1;
|
||||
else if (!strcmp(argv[index], "contains"))
|
||||
mode = 2;
|
||||
else if (!strcmp(argv[index], "name"))
|
||||
mode = 4;
|
||||
else if (!strcmp(argv[index], "address"))
|
||||
mode = 0;
|
||||
if (mode != 0xf)
|
||||
index++;
|
||||
|
||||
if (index >= argc) {
|
||||
kprintf("No area specifier given.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
num = parse_expression(argv[index]);
|
||||
|
||||
if (mode == 0) {
|
||||
dump_area_struct((struct VMArea*)num, mappings);
|
||||
} else {
|
||||
// walk through the area list, looking for the arguments as a name
|
||||
|
||||
VMAreasTree::Iterator it = VMAreas::GetIterator();
|
||||
while ((area = it.Next()) != NULL) {
|
||||
if (((mode & 4) != 0
|
||||
&& !strcmp(argv[index], area->name))
|
||||
|| (num != 0 && (((mode & 1) != 0 && (addr_t)area->id == num)
|
||||
|| (((mode & 2) != 0 && area->Base() <= num
|
||||
&& area->Base() + area->Size() > num))))) {
|
||||
dump_area_struct(area, mappings);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
kprintf("could not find area %s (%ld)\n", argv[index], num);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_area_list(int argc, char** argv)
|
||||
{
|
||||
VMArea* area;
|
||||
const char* name = NULL;
|
||||
int32 id = 0;
|
||||
|
||||
if (argc > 1) {
|
||||
id = parse_expression(argv[1]);
|
||||
if (id == 0)
|
||||
name = argv[1];
|
||||
}
|
||||
|
||||
kprintf("%-*s id %-*s %-*sprotect lock name\n",
|
||||
B_PRINTF_POINTER_WIDTH, "addr", B_PRINTF_POINTER_WIDTH, "base",
|
||||
B_PRINTF_POINTER_WIDTH, "size");
|
||||
|
||||
VMAreasTree::Iterator it = VMAreas::GetIterator();
|
||||
while ((area = it.Next()) != NULL) {
|
||||
if ((id != 0 && area->address_space->ID() != id)
|
||||
|| (name != NULL && strstr(area->name, name) == NULL))
|
||||
continue;
|
||||
|
||||
kprintf("%p %5" B_PRIx32 " %p %p %4" B_PRIx32 " %4d %s\n", area,
|
||||
area->id, (void*)area->Base(), (void*)area->Size(),
|
||||
area->protection, area->wiring, area->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_available_memory(int argc, char** argv)
|
||||
{
|
||||
kprintf("Available memory: %" B_PRIdOFF "/%" B_PRIuPHYSADDR " bytes\n",
|
||||
sAvailableMemory, (phys_addr_t)vm_page_num_pages() * B_PAGE_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_mapping_info(int argc, char** argv)
|
||||
{
|
||||
bool reverseLookup = false;
|
||||
bool pageLookup = false;
|
||||
|
||||
int argi = 1;
|
||||
for (; argi < argc && argv[argi][0] == '-'; argi++) {
|
||||
const char* arg = argv[argi];
|
||||
if (strcmp(arg, "-r") == 0) {
|
||||
reverseLookup = true;
|
||||
} else if (strcmp(arg, "-p") == 0) {
|
||||
reverseLookup = true;
|
||||
pageLookup = true;
|
||||
} else {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// We need at least one argument, the address. Optionally a thread ID can be
|
||||
// specified.
|
||||
if (argi >= argc || argi + 2 < argc) {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64 addressValue;
|
||||
if (!evaluate_debug_expression(argv[argi++], &addressValue, false))
|
||||
return 0;
|
||||
|
||||
Team* team = NULL;
|
||||
if (argi < argc) {
|
||||
uint64 threadID;
|
||||
if (!evaluate_debug_expression(argv[argi++], &threadID, false))
|
||||
return 0;
|
||||
|
||||
Thread* thread = Thread::GetDebug(threadID);
|
||||
if (thread == NULL) {
|
||||
kprintf("Invalid thread/team ID \"%s\"\n", argv[argi - 1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
team = thread->team;
|
||||
}
|
||||
|
||||
if (reverseLookup) {
|
||||
phys_addr_t physicalAddress;
|
||||
if (pageLookup) {
|
||||
vm_page* page = (vm_page*)(addr_t)addressValue;
|
||||
physicalAddress = page->physical_page_number * B_PAGE_SIZE;
|
||||
} else {
|
||||
physicalAddress = (phys_addr_t)addressValue;
|
||||
physicalAddress -= physicalAddress % B_PAGE_SIZE;
|
||||
}
|
||||
|
||||
kprintf(" Team Virtual Address Area\n");
|
||||
kprintf("--------------------------------------\n");
|
||||
|
||||
struct Callback : VMTranslationMap::ReverseMappingInfoCallback {
|
||||
Callback()
|
||||
:
|
||||
fAddressSpace(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void SetAddressSpace(VMAddressSpace* addressSpace)
|
||||
{
|
||||
fAddressSpace = addressSpace;
|
||||
}
|
||||
|
||||
virtual bool HandleVirtualAddress(addr_t virtualAddress)
|
||||
{
|
||||
kprintf("%8" B_PRId32 " %#18" B_PRIxADDR, fAddressSpace->ID(),
|
||||
virtualAddress);
|
||||
if (VMArea* area = fAddressSpace->LookupArea(virtualAddress))
|
||||
kprintf(" %8" B_PRId32 " %s\n", area->id, area->name);
|
||||
else
|
||||
kprintf("\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
VMAddressSpace* fAddressSpace;
|
||||
} callback;
|
||||
|
||||
if (team != NULL) {
|
||||
// team specified -- get its address space
|
||||
VMAddressSpace* addressSpace = team->address_space;
|
||||
if (addressSpace == NULL) {
|
||||
kprintf("Failed to get address space!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
callback.SetAddressSpace(addressSpace);
|
||||
addressSpace->TranslationMap()->DebugGetReverseMappingInfo(
|
||||
physicalAddress, callback);
|
||||
} else {
|
||||
// no team specified -- iterate through all address spaces
|
||||
for (VMAddressSpace* addressSpace = VMAddressSpace::DebugFirst();
|
||||
addressSpace != NULL;
|
||||
addressSpace = VMAddressSpace::DebugNext(addressSpace)) {
|
||||
callback.SetAddressSpace(addressSpace);
|
||||
addressSpace->TranslationMap()->DebugGetReverseMappingInfo(
|
||||
physicalAddress, callback);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// get the address space
|
||||
addr_t virtualAddress = (addr_t)addressValue;
|
||||
virtualAddress -= virtualAddress % B_PAGE_SIZE;
|
||||
VMAddressSpace* addressSpace;
|
||||
if (IS_KERNEL_ADDRESS(virtualAddress)) {
|
||||
addressSpace = VMAddressSpace::Kernel();
|
||||
} else if (team != NULL) {
|
||||
addressSpace = team->address_space;
|
||||
} else {
|
||||
Thread* thread = debug_get_debugged_thread();
|
||||
if (thread == NULL || thread->team == NULL) {
|
||||
kprintf("Failed to get team!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
addressSpace = thread->team->address_space;
|
||||
}
|
||||
|
||||
if (addressSpace == NULL) {
|
||||
kprintf("Failed to get address space!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// let the translation map implementation do the job
|
||||
addressSpace->TranslationMap()->DebugPrintMappingInfo(virtualAddress);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! Deletes all areas and reserved regions in the given address space.
|
||||
|
||||
The caller must ensure that none of the areas has any wired ranges.
|
||||
@@ -4620,59 +3896,7 @@ vm_init(kernel_args* args)
|
||||
|
||||
create_page_mappings_object_caches();
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
if (vm_page_num_free_pages() >= 200 * 1024 * 1024 / B_PAGE_SIZE) {
|
||||
virtual_address_restrictions virtualRestrictions = {};
|
||||
virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
|
||||
physical_address_restrictions physicalRestrictions = {};
|
||||
create_area_etc(VMAddressSpace::KernelID(), "cache info table",
|
||||
ROUNDUP(kCacheInfoTableCount * sizeof(cache_info), B_PAGE_SIZE),
|
||||
B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
|
||||
CREATE_AREA_DONT_WAIT, 0, &virtualRestrictions,
|
||||
&physicalRestrictions, (void**)&sCacheInfoTable);
|
||||
}
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
// add some debugger commands
|
||||
add_debugger_command("areas", &dump_area_list, "Dump a list of all areas");
|
||||
add_debugger_command("area", &dump_area,
|
||||
"Dump info about a particular area");
|
||||
add_debugger_command("cache", &dump_cache, "Dump VMCache");
|
||||
add_debugger_command("cache_tree", &dump_cache_tree, "Dump VMCache tree");
|
||||
#if DEBUG_CACHE_LIST
|
||||
if (sCacheInfoTable != NULL) {
|
||||
add_debugger_command_etc("caches", &dump_caches,
|
||||
"List all VMCache trees",
|
||||
"[ \"-c\" ]\n"
|
||||
"All cache trees are listed sorted in decreasing order by number "
|
||||
"of\n"
|
||||
"used pages or, if \"-c\" is specified, by size of committed "
|
||||
"memory.\n",
|
||||
0);
|
||||
}
|
||||
#endif
|
||||
add_debugger_command("avail", &dump_available_memory,
|
||||
"Dump available memory");
|
||||
add_debugger_command("dl", &display_mem, "dump memory long words (64-bit)");
|
||||
add_debugger_command("dw", &display_mem, "dump memory words (32-bit)");
|
||||
add_debugger_command("ds", &display_mem, "dump memory shorts (16-bit)");
|
||||
add_debugger_command("db", &display_mem, "dump memory bytes (8-bit)");
|
||||
add_debugger_command("string", &display_mem, "dump strings");
|
||||
|
||||
add_debugger_command_etc("mapping", &dump_mapping_info,
|
||||
"Print address mapping information",
|
||||
"[ \"-r\" | \"-p\" ] <address> [ <thread ID> ]\n"
|
||||
"Prints low-level page mapping information for a given address. If\n"
|
||||
"neither \"-r\" nor \"-p\" are specified, <address> is a virtual\n"
|
||||
"address that is looked up in the translation map of the current\n"
|
||||
"team, respectively the team specified by thread ID <thread ID>. If\n"
|
||||
"\"-r\" is specified, <address> is a physical address that is\n"
|
||||
"searched in the translation map of all teams, respectively the team\n"
|
||||
"specified by thread ID <thread ID>. If \"-p\" is specified,\n"
|
||||
"<address> is the address of a vm_page structure. The behavior is\n"
|
||||
"equivalent to specifying \"-r\" with the physical address of that\n"
|
||||
"page.\n",
|
||||
0);
|
||||
vm_debug_init();
|
||||
|
||||
TRACE(("vm_init: exit\n"));
|
||||
|
||||
@@ -5354,6 +4578,16 @@ vm_available_memory(void)
|
||||
}
|
||||
|
||||
|
||||
/*! Like vm_available_memory(), but only for use in the kernel
|
||||
debugger.
|
||||
*/
|
||||
off_t
|
||||
vm_available_memory_debug(void)
|
||||
{
|
||||
return sAvailableMemory;
|
||||
}
|
||||
|
||||
|
||||
off_t
|
||||
vm_available_not_needed_memory(void)
|
||||
{
|
||||
@@ -5710,93 +4944,6 @@ vm_memcpy_physical_page(phys_addr_t to, phys_addr_t from)
|
||||
}
|
||||
|
||||
|
||||
/*! Copies a range of memory directly from/to a page that might not be mapped
|
||||
at the moment.
|
||||
|
||||
For \a unsafeMemory the current mapping (if any is ignored). The function
|
||||
walks through the respective area's cache chain to find the physical page
|
||||
and copies from/to it directly.
|
||||
The memory range starting at \a unsafeMemory with a length of \a size bytes
|
||||
must not cross a page boundary.
|
||||
|
||||
\param teamID The team ID identifying the address space \a unsafeMemory is
|
||||
to be interpreted in. Ignored, if \a unsafeMemory is a kernel address
|
||||
(the kernel address space is assumed in this case). If \c B_CURRENT_TEAM
|
||||
is passed, the address space of the thread returned by
|
||||
debug_get_debugged_thread() is used.
|
||||
\param unsafeMemory The start of the unsafe memory range to be copied
|
||||
from/to.
|
||||
\param buffer A safely accessible kernel buffer to be copied from/to.
|
||||
\param size The number of bytes to be copied.
|
||||
\param copyToUnsafe If \c true, memory is copied from \a buffer to
|
||||
\a unsafeMemory, the other way around otherwise.
|
||||
*/
|
||||
status_t
|
||||
vm_debug_copy_page_memory(team_id teamID, void* unsafeMemory, void* buffer,
|
||||
size_t size, bool copyToUnsafe)
|
||||
{
|
||||
if (size > B_PAGE_SIZE || ROUNDDOWN((addr_t)unsafeMemory, B_PAGE_SIZE)
|
||||
!= ROUNDDOWN((addr_t)unsafeMemory + size - 1, B_PAGE_SIZE)) {
|
||||
return B_BAD_VALUE;
|
||||
}
|
||||
|
||||
// get the address space for the debugged thread
|
||||
VMAddressSpace* addressSpace;
|
||||
if (IS_KERNEL_ADDRESS(unsafeMemory)) {
|
||||
addressSpace = VMAddressSpace::Kernel();
|
||||
} else if (teamID == B_CURRENT_TEAM) {
|
||||
Thread* thread = debug_get_debugged_thread();
|
||||
if (thread == NULL || thread->team == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
addressSpace = thread->team->address_space;
|
||||
} else
|
||||
addressSpace = VMAddressSpace::DebugGet(teamID);
|
||||
|
||||
if (addressSpace == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// get the area
|
||||
VMArea* area = addressSpace->LookupArea((addr_t)unsafeMemory);
|
||||
if (area == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// search the page
|
||||
off_t cacheOffset = (addr_t)unsafeMemory - area->Base()
|
||||
+ area->cache_offset;
|
||||
VMCache* cache = area->cache;
|
||||
vm_page* page = NULL;
|
||||
while (cache != NULL) {
|
||||
page = cache->DebugLookupPage(cacheOffset);
|
||||
if (page != NULL)
|
||||
break;
|
||||
|
||||
// Page not found in this cache -- if it is paged out, we must not try
|
||||
// to get it from lower caches.
|
||||
if (cache->DebugHasPage(cacheOffset))
|
||||
break;
|
||||
|
||||
cache = cache->source;
|
||||
}
|
||||
|
||||
if (page == NULL)
|
||||
return B_UNSUPPORTED;
|
||||
|
||||
// copy from/to physical memory
|
||||
phys_addr_t physicalAddress = page->physical_page_number * B_PAGE_SIZE
|
||||
+ (addr_t)unsafeMemory % B_PAGE_SIZE;
|
||||
|
||||
if (copyToUnsafe) {
|
||||
if (page->Cache() != area->cache)
|
||||
return B_UNSUPPORTED;
|
||||
|
||||
return vm_memcpy_to_physical(physicalAddress, buffer, size, false);
|
||||
}
|
||||
|
||||
return vm_memcpy_from_physical(buffer, physicalAddress, size, false);
|
||||
}
|
||||
|
||||
|
||||
/** Validate that a memory range is either fully in kernel space, or fully in
|
||||
* userspace */
|
||||
static inline bool
|
||||
|
||||
@@ -0,0 +1,890 @@
|
||||
/*
|
||||
* Copyright 2024, Haiku, Inc. All rights reserved.
|
||||
* Copyright 2010-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
|
||||
* Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
|
||||
* Distributed under the terms of the NewOS License.
|
||||
*/
|
||||
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <vm/vm_page.h>
|
||||
#include <vm/vm.h>
|
||||
#include <vm/vm_priv.h>
|
||||
#include <vm/VMAddressSpace.h>
|
||||
#include <vm/VMArea.h>
|
||||
#include <vm/VMCache.h>
|
||||
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
|
||||
struct cache_info {
|
||||
VMCache* cache;
|
||||
addr_t page_count;
|
||||
addr_t committed;
|
||||
};
|
||||
|
||||
static const uint32 kCacheInfoTableCount = 100 * 1024;
|
||||
static cache_info* sCacheInfoTable;
|
||||
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
|
||||
static int
|
||||
display_mem(int argc, char** argv)
|
||||
{
|
||||
bool physical = false;
|
||||
addr_t copyAddress;
|
||||
int32 displayWidth;
|
||||
int32 itemSize;
|
||||
int32 num = -1;
|
||||
addr_t address;
|
||||
int i = 1, j;
|
||||
|
||||
if (argc > 1 && argv[1][0] == '-') {
|
||||
if (!strcmp(argv[1], "-p") || !strcmp(argv[1], "--physical")) {
|
||||
physical = true;
|
||||
i++;
|
||||
} else
|
||||
i = 99;
|
||||
}
|
||||
|
||||
if (argc < i + 1 || argc > i + 2) {
|
||||
kprintf("usage: dl/dw/ds/db/string [-p|--physical] <address> [num]\n"
|
||||
"\tdl - 8 bytes\n"
|
||||
"\tdw - 4 bytes\n"
|
||||
"\tds - 2 bytes\n"
|
||||
"\tdb - 1 byte\n"
|
||||
"\tstring - a whole string\n"
|
||||
" -p or --physical only allows memory from a single page to be "
|
||||
"displayed.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
address = parse_expression(argv[i]);
|
||||
|
||||
if (argc > i + 1)
|
||||
num = parse_expression(argv[i + 1]);
|
||||
|
||||
// build the format string
|
||||
if (strcmp(argv[0], "db") == 0) {
|
||||
itemSize = 1;
|
||||
displayWidth = 16;
|
||||
} else if (strcmp(argv[0], "ds") == 0) {
|
||||
itemSize = 2;
|
||||
displayWidth = 8;
|
||||
} else if (strcmp(argv[0], "dw") == 0) {
|
||||
itemSize = 4;
|
||||
displayWidth = 4;
|
||||
} else if (strcmp(argv[0], "dl") == 0) {
|
||||
itemSize = 8;
|
||||
displayWidth = 2;
|
||||
} else if (strcmp(argv[0], "string") == 0) {
|
||||
itemSize = 1;
|
||||
displayWidth = -1;
|
||||
} else {
|
||||
kprintf("display_mem called in an invalid way!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (num <= 0)
|
||||
num = displayWidth;
|
||||
|
||||
void* physicalPageHandle = NULL;
|
||||
|
||||
if (physical) {
|
||||
int32 offset = address & (B_PAGE_SIZE - 1);
|
||||
if (num * itemSize + offset > B_PAGE_SIZE) {
|
||||
num = (B_PAGE_SIZE - offset) / itemSize;
|
||||
kprintf("NOTE: number of bytes has been cut to page size\n");
|
||||
}
|
||||
|
||||
address = ROUNDDOWN(address, B_PAGE_SIZE);
|
||||
|
||||
if (vm_get_physical_page_debug(address, ©Address,
|
||||
&physicalPageHandle) != B_OK) {
|
||||
kprintf("getting the hardware page failed.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
address += offset;
|
||||
copyAddress += offset;
|
||||
} else
|
||||
copyAddress = address;
|
||||
|
||||
if (!strcmp(argv[0], "string")) {
|
||||
kprintf("%p \"", (char*)copyAddress);
|
||||
|
||||
// string mode
|
||||
for (i = 0; true; i++) {
|
||||
char c;
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &c, (char*)copyAddress + i, 1)
|
||||
!= B_OK
|
||||
|| c == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
kprintf("\\n");
|
||||
else if (c == '\t')
|
||||
kprintf("\\t");
|
||||
else {
|
||||
if (!isprint(c))
|
||||
c = '.';
|
||||
|
||||
kprintf("%c", c);
|
||||
}
|
||||
}
|
||||
|
||||
kprintf("\"\n");
|
||||
} else {
|
||||
// number mode
|
||||
for (i = 0; i < num; i++) {
|
||||
uint64 value;
|
||||
|
||||
if ((i % displayWidth) == 0) {
|
||||
int32 displayed = min_c(displayWidth, (num-i)) * itemSize;
|
||||
if (i != 0)
|
||||
kprintf("\n");
|
||||
|
||||
kprintf("[0x%lx] ", address + i * itemSize);
|
||||
|
||||
for (j = 0; j < displayed; j++) {
|
||||
char c;
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &c,
|
||||
(char*)copyAddress + i * itemSize + j, 1) != B_OK) {
|
||||
displayed = j;
|
||||
break;
|
||||
}
|
||||
if (!isprint(c))
|
||||
c = '.';
|
||||
|
||||
kprintf("%c", c);
|
||||
}
|
||||
if (num > displayWidth) {
|
||||
// make sure the spacing in the last line is correct
|
||||
for (j = displayed; j < displayWidth * itemSize; j++)
|
||||
kprintf(" ");
|
||||
}
|
||||
kprintf(" ");
|
||||
}
|
||||
|
||||
if (debug_memcpy(B_CURRENT_TEAM, &value,
|
||||
(uint8*)copyAddress + i * itemSize, itemSize) != B_OK) {
|
||||
kprintf("read fault");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (itemSize) {
|
||||
case 1:
|
||||
kprintf(" %02" B_PRIx8, *(uint8*)&value);
|
||||
break;
|
||||
case 2:
|
||||
kprintf(" %04" B_PRIx16, *(uint16*)&value);
|
||||
break;
|
||||
case 4:
|
||||
kprintf(" %08" B_PRIx32, *(uint32*)&value);
|
||||
break;
|
||||
case 8:
|
||||
kprintf(" %016" B_PRIx64, *(uint64*)&value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kprintf("\n");
|
||||
}
|
||||
|
||||
if (physical) {
|
||||
copyAddress = ROUNDDOWN(copyAddress, B_PAGE_SIZE);
|
||||
vm_put_physical_page_debug(copyAddress, physicalPageHandle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_cache_tree_recursively(VMCache* cache, int level,
|
||||
VMCache* highlightCache)
|
||||
{
|
||||
// print this cache
|
||||
for (int i = 0; i < level; i++)
|
||||
kprintf(" ");
|
||||
if (cache == highlightCache)
|
||||
kprintf("%p <--\n", cache);
|
||||
else
|
||||
kprintf("%p\n", cache);
|
||||
|
||||
// recursively print its consumers
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
dump_cache_tree_recursively(consumer, level + 1, highlightCache);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_cache_tree(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: %s <address>\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t address = parse_expression(argv[1]);
|
||||
if (address == 0)
|
||||
return 0;
|
||||
|
||||
VMCache* cache = (VMCache*)address;
|
||||
VMCache* root = cache;
|
||||
|
||||
// find the root cache (the transitive source)
|
||||
while (root->source != NULL)
|
||||
root = root->source;
|
||||
|
||||
dump_cache_tree_recursively(root, 0, cache);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
vm_cache_type_to_string(int32 type)
|
||||
{
|
||||
switch (type) {
|
||||
case CACHE_TYPE_RAM:
|
||||
return "RAM";
|
||||
case CACHE_TYPE_DEVICE:
|
||||
return "device";
|
||||
case CACHE_TYPE_VNODE:
|
||||
return "vnode";
|
||||
case CACHE_TYPE_NULL:
|
||||
return "null";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
|
||||
static void
|
||||
update_cache_info_recursively(VMCache* cache, cache_info& info)
|
||||
{
|
||||
info.page_count += cache->page_count;
|
||||
if (cache->type == CACHE_TYPE_RAM)
|
||||
info.committed += cache->committed_size;
|
||||
|
||||
// recurse
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
update_cache_info_recursively(consumer, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cache_info_compare_page_count(const void* _a, const void* _b)
|
||||
{
|
||||
const cache_info* a = (const cache_info*)_a;
|
||||
const cache_info* b = (const cache_info*)_b;
|
||||
if (a->page_count == b->page_count)
|
||||
return 0;
|
||||
return a->page_count < b->page_count ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cache_info_compare_committed(const void* _a, const void* _b)
|
||||
{
|
||||
const cache_info* a = (const cache_info*)_a;
|
||||
const cache_info* b = (const cache_info*)_b;
|
||||
if (a->committed == b->committed)
|
||||
return 0;
|
||||
return a->committed < b->committed ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_caches_recursively(VMCache* cache, cache_info& info, int level)
|
||||
{
|
||||
for (int i = 0; i < level; i++)
|
||||
kprintf(" ");
|
||||
|
||||
kprintf("%p: type: %s, base: %" B_PRIdOFF ", size: %" B_PRIdOFF ", "
|
||||
"pages: %" B_PRIu32, cache, vm_cache_type_to_string(cache->type),
|
||||
cache->virtual_base, cache->virtual_end, cache->page_count);
|
||||
|
||||
if (level == 0)
|
||||
kprintf("/%lu", info.page_count);
|
||||
|
||||
if (cache->type == CACHE_TYPE_RAM || (level == 0 && info.committed > 0)) {
|
||||
kprintf(", committed: %" B_PRIdOFF, cache->committed_size);
|
||||
|
||||
if (level == 0)
|
||||
kprintf("/%lu", info.committed);
|
||||
}
|
||||
|
||||
// areas
|
||||
if (cache->areas != NULL) {
|
||||
VMArea* area = cache->areas;
|
||||
kprintf(", areas: %" B_PRId32 " (%s, team: %" B_PRId32 ")", area->id,
|
||||
area->name, area->address_space->ID());
|
||||
|
||||
while (area->cache_next != NULL) {
|
||||
area = area->cache_next;
|
||||
kprintf(", %" B_PRId32, area->id);
|
||||
}
|
||||
}
|
||||
|
||||
kputs("\n");
|
||||
|
||||
// recurse
|
||||
for (VMCache::ConsumerList::Iterator it = cache->consumers.GetIterator();
|
||||
VMCache* consumer = it.Next();) {
|
||||
dump_caches_recursively(consumer, info, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_caches(int argc, char** argv)
|
||||
{
|
||||
if (sCacheInfoTable == NULL) {
|
||||
kprintf("No cache info table!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool sortByPageCount = true;
|
||||
|
||||
for (int32 i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-c") == 0) {
|
||||
sortByPageCount = false;
|
||||
} else {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 totalCount = 0;
|
||||
uint32 rootCount = 0;
|
||||
off_t totalCommitted = 0;
|
||||
page_num_t totalPages = 0;
|
||||
|
||||
VMCache* cache = gDebugCacheList;
|
||||
while (cache) {
|
||||
totalCount++;
|
||||
if (cache->source == NULL) {
|
||||
cache_info stackInfo;
|
||||
cache_info& info = rootCount < kCacheInfoTableCount
|
||||
? sCacheInfoTable[rootCount] : stackInfo;
|
||||
rootCount++;
|
||||
info.cache = cache;
|
||||
info.page_count = 0;
|
||||
info.committed = 0;
|
||||
update_cache_info_recursively(cache, info);
|
||||
totalCommitted += info.committed;
|
||||
totalPages += info.page_count;
|
||||
}
|
||||
|
||||
cache = cache->debug_next;
|
||||
}
|
||||
|
||||
kprintf("total committed memory: %" B_PRIdOFF ", total used pages: %"
|
||||
B_PRIuPHYSADDR "\n", totalCommitted, totalPages);
|
||||
kprintf("%" B_PRIu32 " caches (%" B_PRIu32 " root caches), sorted by %s "
|
||||
"per cache tree...\n\n", totalCount, rootCount, sortByPageCount ?
|
||||
"page count" : "committed size");
|
||||
|
||||
if (rootCount > kCacheInfoTableCount) {
|
||||
kprintf("Cache info table too small! Can't sort and print caches!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
qsort(sCacheInfoTable, rootCount, sizeof(cache_info),
|
||||
sortByPageCount
|
||||
? &cache_info_compare_page_count
|
||||
: &cache_info_compare_committed);
|
||||
|
||||
for (uint32 i = 0; i < rootCount; i++) {
|
||||
cache_info& info = sCacheInfoTable[i];
|
||||
dump_caches_recursively(info.cache, info, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
|
||||
static int
|
||||
dump_cache(int argc, char** argv)
|
||||
{
|
||||
VMCache* cache;
|
||||
bool showPages = false;
|
||||
int i = 1;
|
||||
|
||||
if (argc < 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: %s [-ps] <address>\n"
|
||||
" if -p is specified, all pages are shown, if -s is used\n"
|
||||
" only the cache info is shown respectively.\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
while (argv[i][0] == '-') {
|
||||
char* arg = argv[i] + 1;
|
||||
while (arg[0]) {
|
||||
if (arg[0] == 'p')
|
||||
showPages = true;
|
||||
arg++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (argv[i] == NULL) {
|
||||
kprintf("%s: invalid argument, pass address\n", argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t address = parse_expression(argv[i]);
|
||||
if (address == 0)
|
||||
return 0;
|
||||
|
||||
cache = (VMCache*)address;
|
||||
|
||||
cache->Dump(showPages);
|
||||
|
||||
set_debug_variable("_sourceCache", (addr_t)cache->source);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_area_struct(VMArea* area, bool mappings)
|
||||
{
|
||||
kprintf("AREA: %p\n", area);
|
||||
kprintf("name:\t\t'%s'\n", area->name);
|
||||
kprintf("owner:\t\t0x%" B_PRIx32 "\n", area->address_space->ID());
|
||||
kprintf("id:\t\t0x%" B_PRIx32 "\n", area->id);
|
||||
kprintf("base:\t\t0x%lx\n", area->Base());
|
||||
kprintf("size:\t\t0x%lx\n", area->Size());
|
||||
kprintf("protection:\t0x%" B_PRIx32 "\n", area->protection);
|
||||
kprintf("page_protection:%p\n", area->page_protections);
|
||||
kprintf("wiring:\t\t0x%x\n", area->wiring);
|
||||
kprintf("memory_type:\t%#" B_PRIx32 "\n", area->MemoryType());
|
||||
kprintf("cache:\t\t%p\n", area->cache);
|
||||
kprintf("cache_type:\t%s\n", vm_cache_type_to_string(area->cache_type));
|
||||
kprintf("cache_offset:\t0x%" B_PRIx64 "\n", area->cache_offset);
|
||||
kprintf("cache_next:\t%p\n", area->cache_next);
|
||||
kprintf("cache_prev:\t%p\n", area->cache_prev);
|
||||
|
||||
VMAreaMappings::Iterator iterator = area->mappings.GetIterator();
|
||||
if (mappings) {
|
||||
kprintf("page mappings:\n");
|
||||
while (iterator.HasNext()) {
|
||||
vm_page_mapping* mapping = iterator.Next();
|
||||
kprintf(" %p", mapping->page);
|
||||
}
|
||||
kprintf("\n");
|
||||
} else {
|
||||
uint32 count = 0;
|
||||
while (iterator.Next() != NULL) {
|
||||
count++;
|
||||
}
|
||||
kprintf("page mappings:\t%" B_PRIu32 "\n", count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_area(int argc, char** argv)
|
||||
{
|
||||
bool mappings = false;
|
||||
bool found = false;
|
||||
int32 index = 1;
|
||||
VMArea* area;
|
||||
addr_t num;
|
||||
|
||||
if (argc < 2 || !strcmp(argv[1], "--help")) {
|
||||
kprintf("usage: area [-m] [id|contains|address|name] <id|address|name>\n"
|
||||
"All areas matching either id/address/name are listed. You can\n"
|
||||
"force to check only a specific item by prefixing the specifier\n"
|
||||
"with the id/contains/address/name keywords.\n"
|
||||
"-m shows the area's mappings as well.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[1], "-m")) {
|
||||
mappings = true;
|
||||
index++;
|
||||
}
|
||||
|
||||
int32 mode = 0xf;
|
||||
if (!strcmp(argv[index], "id"))
|
||||
mode = 1;
|
||||
else if (!strcmp(argv[index], "contains"))
|
||||
mode = 2;
|
||||
else if (!strcmp(argv[index], "name"))
|
||||
mode = 4;
|
||||
else if (!strcmp(argv[index], "address"))
|
||||
mode = 0;
|
||||
if (mode != 0xf)
|
||||
index++;
|
||||
|
||||
if (index >= argc) {
|
||||
kprintf("No area specifier given.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
num = parse_expression(argv[index]);
|
||||
|
||||
if (mode == 0) {
|
||||
dump_area_struct((struct VMArea*)num, mappings);
|
||||
} else {
|
||||
// walk through the area list, looking for the arguments as a name
|
||||
|
||||
VMAreasTree::Iterator it = VMAreas::GetIterator();
|
||||
while ((area = it.Next()) != NULL) {
|
||||
if (((mode & 4) != 0
|
||||
&& !strcmp(argv[index], area->name))
|
||||
|| (num != 0 && (((mode & 1) != 0 && (addr_t)area->id == num)
|
||||
|| (((mode & 2) != 0 && area->Base() <= num
|
||||
&& area->Base() + area->Size() > num))))) {
|
||||
dump_area_struct(area, mappings);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
kprintf("could not find area %s (%ld)\n", argv[index], num);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_area_list(int argc, char** argv)
|
||||
{
|
||||
VMArea* area;
|
||||
const char* name = NULL;
|
||||
int32 id = 0;
|
||||
|
||||
if (argc > 1) {
|
||||
id = parse_expression(argv[1]);
|
||||
if (id == 0)
|
||||
name = argv[1];
|
||||
}
|
||||
|
||||
kprintf("%-*s id %-*s %-*sprotect lock name\n",
|
||||
B_PRINTF_POINTER_WIDTH, "addr", B_PRINTF_POINTER_WIDTH, "base",
|
||||
B_PRINTF_POINTER_WIDTH, "size");
|
||||
|
||||
VMAreasTree::Iterator it = VMAreas::GetIterator();
|
||||
while ((area = it.Next()) != NULL) {
|
||||
if ((id != 0 && area->address_space->ID() != id)
|
||||
|| (name != NULL && strstr(area->name, name) == NULL))
|
||||
continue;
|
||||
|
||||
kprintf("%p %5" B_PRIx32 " %p %p %4" B_PRIx32 " %4d %s\n", area,
|
||||
area->id, (void*)area->Base(), (void*)area->Size(),
|
||||
area->protection, area->wiring, area->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_available_memory(int argc, char** argv)
|
||||
{
|
||||
kprintf("Available memory: %" B_PRIdOFF "/%" B_PRIuPHYSADDR " bytes\n",
|
||||
vm_available_memory_debug(), (phys_addr_t)vm_page_num_pages() * B_PAGE_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_mapping_info(int argc, char** argv)
|
||||
{
|
||||
bool reverseLookup = false;
|
||||
bool pageLookup = false;
|
||||
|
||||
int argi = 1;
|
||||
for (; argi < argc && argv[argi][0] == '-'; argi++) {
|
||||
const char* arg = argv[argi];
|
||||
if (strcmp(arg, "-r") == 0) {
|
||||
reverseLookup = true;
|
||||
} else if (strcmp(arg, "-p") == 0) {
|
||||
reverseLookup = true;
|
||||
pageLookup = true;
|
||||
} else {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// We need at least one argument, the address. Optionally a thread ID can be
|
||||
// specified.
|
||||
if (argi >= argc || argi + 2 < argc) {
|
||||
print_debugger_command_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64 addressValue;
|
||||
if (!evaluate_debug_expression(argv[argi++], &addressValue, false))
|
||||
return 0;
|
||||
|
||||
Team* team = NULL;
|
||||
if (argi < argc) {
|
||||
uint64 threadID;
|
||||
if (!evaluate_debug_expression(argv[argi++], &threadID, false))
|
||||
return 0;
|
||||
|
||||
Thread* thread = Thread::GetDebug(threadID);
|
||||
if (thread == NULL) {
|
||||
kprintf("Invalid thread/team ID \"%s\"\n", argv[argi - 1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
team = thread->team;
|
||||
}
|
||||
|
||||
if (reverseLookup) {
|
||||
phys_addr_t physicalAddress;
|
||||
if (pageLookup) {
|
||||
vm_page* page = (vm_page*)(addr_t)addressValue;
|
||||
physicalAddress = page->physical_page_number * B_PAGE_SIZE;
|
||||
} else {
|
||||
physicalAddress = (phys_addr_t)addressValue;
|
||||
physicalAddress -= physicalAddress % B_PAGE_SIZE;
|
||||
}
|
||||
|
||||
kprintf(" Team Virtual Address Area\n");
|
||||
kprintf("--------------------------------------\n");
|
||||
|
||||
struct Callback : VMTranslationMap::ReverseMappingInfoCallback {
|
||||
Callback()
|
||||
:
|
||||
fAddressSpace(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void SetAddressSpace(VMAddressSpace* addressSpace)
|
||||
{
|
||||
fAddressSpace = addressSpace;
|
||||
}
|
||||
|
||||
virtual bool HandleVirtualAddress(addr_t virtualAddress)
|
||||
{
|
||||
kprintf("%8" B_PRId32 " %#18" B_PRIxADDR, fAddressSpace->ID(),
|
||||
virtualAddress);
|
||||
if (VMArea* area = fAddressSpace->LookupArea(virtualAddress))
|
||||
kprintf(" %8" B_PRId32 " %s\n", area->id, area->name);
|
||||
else
|
||||
kprintf("\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
VMAddressSpace* fAddressSpace;
|
||||
} callback;
|
||||
|
||||
if (team != NULL) {
|
||||
// team specified -- get its address space
|
||||
VMAddressSpace* addressSpace = team->address_space;
|
||||
if (addressSpace == NULL) {
|
||||
kprintf("Failed to get address space!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
callback.SetAddressSpace(addressSpace);
|
||||
addressSpace->TranslationMap()->DebugGetReverseMappingInfo(
|
||||
physicalAddress, callback);
|
||||
} else {
|
||||
// no team specified -- iterate through all address spaces
|
||||
for (VMAddressSpace* addressSpace = VMAddressSpace::DebugFirst();
|
||||
addressSpace != NULL;
|
||||
addressSpace = VMAddressSpace::DebugNext(addressSpace)) {
|
||||
callback.SetAddressSpace(addressSpace);
|
||||
addressSpace->TranslationMap()->DebugGetReverseMappingInfo(
|
||||
physicalAddress, callback);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// get the address space
|
||||
addr_t virtualAddress = (addr_t)addressValue;
|
||||
virtualAddress -= virtualAddress % B_PAGE_SIZE;
|
||||
VMAddressSpace* addressSpace;
|
||||
if (IS_KERNEL_ADDRESS(virtualAddress)) {
|
||||
addressSpace = VMAddressSpace::Kernel();
|
||||
} else if (team != NULL) {
|
||||
addressSpace = team->address_space;
|
||||
} else {
|
||||
Thread* thread = debug_get_debugged_thread();
|
||||
if (thread == NULL || thread->team == NULL) {
|
||||
kprintf("Failed to get team!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
addressSpace = thread->team->address_space;
|
||||
}
|
||||
|
||||
if (addressSpace == NULL) {
|
||||
kprintf("Failed to get address space!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// let the translation map implementation do the job
|
||||
addressSpace->TranslationMap()->DebugPrintMappingInfo(virtualAddress);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*! Copies a range of memory directly from/to a page that might not be mapped
|
||||
at the moment.
|
||||
|
||||
For \a unsafeMemory the current mapping (if any is ignored). The function
|
||||
walks through the respective area's cache chain to find the physical page
|
||||
and copies from/to it directly.
|
||||
The memory range starting at \a unsafeMemory with a length of \a size bytes
|
||||
must not cross a page boundary.
|
||||
|
||||
\param teamID The team ID identifying the address space \a unsafeMemory is
|
||||
to be interpreted in. Ignored, if \a unsafeMemory is a kernel address
|
||||
(the kernel address space is assumed in this case). If \c B_CURRENT_TEAM
|
||||
is passed, the address space of the thread returned by
|
||||
debug_get_debugged_thread() is used.
|
||||
\param unsafeMemory The start of the unsafe memory range to be copied
|
||||
from/to.
|
||||
\param buffer A safely accessible kernel buffer to be copied from/to.
|
||||
\param size The number of bytes to be copied.
|
||||
\param copyToUnsafe If \c true, memory is copied from \a buffer to
|
||||
\a unsafeMemory, the other way around otherwise.
|
||||
*/
|
||||
status_t
|
||||
vm_debug_copy_page_memory(team_id teamID, void* unsafeMemory, void* buffer,
|
||||
size_t size, bool copyToUnsafe)
|
||||
{
|
||||
if (size > B_PAGE_SIZE || ROUNDDOWN((addr_t)unsafeMemory, B_PAGE_SIZE)
|
||||
!= ROUNDDOWN((addr_t)unsafeMemory + size - 1, B_PAGE_SIZE)) {
|
||||
return B_BAD_VALUE;
|
||||
}
|
||||
|
||||
// get the address space for the debugged thread
|
||||
VMAddressSpace* addressSpace;
|
||||
if (IS_KERNEL_ADDRESS(unsafeMemory)) {
|
||||
addressSpace = VMAddressSpace::Kernel();
|
||||
} else if (teamID == B_CURRENT_TEAM) {
|
||||
Thread* thread = debug_get_debugged_thread();
|
||||
if (thread == NULL || thread->team == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
addressSpace = thread->team->address_space;
|
||||
} else
|
||||
addressSpace = VMAddressSpace::DebugGet(teamID);
|
||||
|
||||
if (addressSpace == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// get the area
|
||||
VMArea* area = addressSpace->LookupArea((addr_t)unsafeMemory);
|
||||
if (area == NULL)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
// search the page
|
||||
off_t cacheOffset = (addr_t)unsafeMemory - area->Base()
|
||||
+ area->cache_offset;
|
||||
VMCache* cache = area->cache;
|
||||
vm_page* page = NULL;
|
||||
while (cache != NULL) {
|
||||
page = cache->DebugLookupPage(cacheOffset);
|
||||
if (page != NULL)
|
||||
break;
|
||||
|
||||
// Page not found in this cache -- if it is paged out, we must not try
|
||||
// to get it from lower caches.
|
||||
if (cache->DebugHasPage(cacheOffset))
|
||||
break;
|
||||
|
||||
cache = cache->source;
|
||||
}
|
||||
|
||||
if (page == NULL)
|
||||
return B_UNSUPPORTED;
|
||||
|
||||
// copy from/to physical memory
|
||||
phys_addr_t physicalAddress = page->physical_page_number * B_PAGE_SIZE
|
||||
+ (addr_t)unsafeMemory % B_PAGE_SIZE;
|
||||
|
||||
if (copyToUnsafe) {
|
||||
if (page->Cache() != area->cache)
|
||||
return B_UNSUPPORTED;
|
||||
|
||||
return vm_memcpy_to_physical(physicalAddress, buffer, size, false);
|
||||
}
|
||||
|
||||
return vm_memcpy_from_physical(buffer, physicalAddress, size, false);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
vm_debug_init()
|
||||
{
|
||||
#if DEBUG_CACHE_LIST
|
||||
if (vm_page_num_free_pages() >= 200 * 1024 * 1024 / B_PAGE_SIZE) {
|
||||
virtual_address_restrictions virtualRestrictions = {};
|
||||
virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
|
||||
physical_address_restrictions physicalRestrictions = {};
|
||||
create_area_etc(VMAddressSpace::KernelID(), "cache info table",
|
||||
ROUNDUP(kCacheInfoTableCount * sizeof(cache_info), B_PAGE_SIZE),
|
||||
B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
|
||||
CREATE_AREA_DONT_WAIT, 0, &virtualRestrictions,
|
||||
&physicalRestrictions, (void**)&sCacheInfoTable);
|
||||
}
|
||||
#endif // DEBUG_CACHE_LIST
|
||||
|
||||
// add some debugger commands
|
||||
add_debugger_command("areas", &dump_area_list, "Dump a list of all areas");
|
||||
add_debugger_command("area", &dump_area,
|
||||
"Dump info about a particular area");
|
||||
add_debugger_command("cache", &dump_cache, "Dump VMCache");
|
||||
add_debugger_command("cache_tree", &dump_cache_tree, "Dump VMCache tree");
|
||||
#if DEBUG_CACHE_LIST
|
||||
if (sCacheInfoTable != NULL) {
|
||||
add_debugger_command_etc("caches", &dump_caches,
|
||||
"List all VMCache trees",
|
||||
"[ \"-c\" ]\n"
|
||||
"All cache trees are listed sorted in decreasing order by number "
|
||||
"of\n"
|
||||
"used pages or, if \"-c\" is specified, by size of committed "
|
||||
"memory.\n",
|
||||
0);
|
||||
}
|
||||
#endif
|
||||
add_debugger_command("avail", &dump_available_memory,
|
||||
"Dump available memory");
|
||||
add_debugger_command("dl", &display_mem, "dump memory long words (64-bit)");
|
||||
add_debugger_command("dw", &display_mem, "dump memory words (32-bit)");
|
||||
add_debugger_command("ds", &display_mem, "dump memory shorts (16-bit)");
|
||||
add_debugger_command("db", &display_mem, "dump memory bytes (8-bit)");
|
||||
add_debugger_command("string", &display_mem, "dump strings");
|
||||
|
||||
add_debugger_command_etc("mapping", &dump_mapping_info,
|
||||
"Print address mapping information",
|
||||
"[ \"-r\" | \"-p\" ] <address> [ <thread ID> ]\n"
|
||||
"Prints low-level page mapping information for a given address. If\n"
|
||||
"neither \"-r\" nor \"-p\" are specified, <address> is a virtual\n"
|
||||
"address that is looked up in the translation map of the current\n"
|
||||
"team, respectively the team specified by thread ID <thread ID>. If\n"
|
||||
"\"-r\" is specified, <address> is a physical address that is\n"
|
||||
"searched in the translation map of all teams, respectively the team\n"
|
||||
"specified by thread ID <thread ID>. If \"-p\" is specified,\n"
|
||||
"<address> is the address of a vm_page structure. The behavior is\n"
|
||||
"equivalent to specifying \"-r\" with the physical address of that\n"
|
||||
"page.\n",
|
||||
0);
|
||||
}
|
||||
Reference in New Issue
Block a user