/* qemu-vtop.c - qemu-vtop extension module for crash * * Copyright (C) 2011, 2012 FUJITSU LIMITED * Auther: Qiao Nuohan * * 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. */ #include "defs.h" /* From the crash source top-level directory */ #define PHYSICAL_ADDR (0x1) #define USER_SPECIFY_TASK (0x4) #define MAXTOKENS (100) struct qemu_vtop_offset_table { long file_private_data; long kvm_memslots; long kvm_memslots_nmemslots; long kvm_memslots_memslots; long kvm_memory_slot_base_gfn; long kvm_memory_slot_npages; long kvm_memory_slot_userspace_addr; }; struct qemu_vtop_size_table { long kvm_memory_slot; }; int _init(void); int _fini(void); void cmd_qemu_vtop(void); char *help_qemu_vtop[]; static void qemu_vtop_init(); static void do_qemu_vtop_physical(ulong, struct task_context *); static ulong get_file_ref(ulong, struct reference *); static ulong parse_for_file(char *); static ulong gpa_to_hva(ulong , ulong ); static void print_vtop(ulong, ulong, struct task_context *); static struct command_table_entry command_table[] = { { "qemu-vtop", cmd_qemu_vtop, help_qemu_vtop, 0 }, /* One or more commands, */ { NULL } /* terminated by NULL, */ }; static struct qemu_vtop_offset_table qemu_vtop_offset_table = { 0 }; static struct qemu_vtop_size_table qemu_vtop_size_table = { 0 }; #define QEMU_VTOP_MEMBER_OFFSET_INIT(X, Y, Z) (qemu_vtop_offset_table.X = MEMBER_OFFSET(Y, Z)) #define QEMU_VTOP_ANON_MEMBER_OFFSET_INIT(X, Y, Z) (qemu_vtop_offset_table.X = ANON_MEMBER_OFFSET(Y, Z)) #define QEMU_VTOP_STRUCT_SIZE_INIT(X, Y) (qemu_vtop_size_table.X = STRUCT_SIZE(Y)) #define QEMU_VTOP_OFFSET(X) (OFFSET_verify(qemu_vtop_offset_table.X, (char *)__FUNCTION__, __FILE__, __LINE__, #X)) #define QEMU_VTOP_SIZE(X) (SIZE_verify(qemu_vtop_size_table.X, (char *)__FUNCTION__, __FILE__, __LINE__, #X)) int _init(void) /* Register the command set. */ { register_extension(command_table); return 1; } /* * The _fini() function is called if the shared object is unloaded. * If desired, perform any cleanups here. */ int _fini(void) { return 1; } void cmd_qemu_vtop(void) { int c; int other; ulong vaddr, context; struct task_context *tc; ulong qemu_vtop_flag; tc = NULL; while ((c = getopt(argcnt, args, "g:p")) != EOF) { switch(c) { case 'g': switch (str_to_context(optarg, &context, &tc)) { case STR_PID: case STR_TASK: qemu_vtop_flag |= USER_SPECIFY_TASK; break; case STR_INVALID: error(FATAL, "invalid task or pid value: %s\n", optarg); break; } break; case 'p': qemu_vtop_flag |= PHYSICAL_ADDR; break; default: argerrs++; break; } } if (argerrs || !args[optind]) cmd_usage(pc->curcmd, SYNOPSIS); if (!(qemu_vtop_flag & USER_SPECIFY_TASK)) error(FATAL, "-g [pid | taskp] must be specified\n"); if ((qemu_vtop_flag & PHYSICAL_ADDR) == 0) error(FATAL, "-p should be specified\n"); qemu_vtop_init(); other = 0; while (args[optind]) { vaddr = htol(args[optind], FAULT_ON_ERROR, NULL); if (other++) fprintf(fp, "\n"); if (qemu_vtop_flag & PHYSICAL_ADDR) do_qemu_vtop_physical(vaddr, tc); optind++; } fprintf(fp, "\n"); } /* * The optional help data is simply an array of strings in a defined format. * For example, the "help qemu-vtop" command will use the help_qemu_vtop[] string * array below to create a help page that looks like this: * * NAME * qemu-vtop - KVM guest's address to host's address * * SYNOPSIS * qemu-vtop -g [pid | taskp] -p address ... * * DESCRIPTION * This command translates a guest's address on a KVM to its host's physical * address. It first gets the host's virtual address related to the guest's * address, then display the information of "vtop ". * * -p address The address is a physical address of a guest * -g [pid | taskp] Translate the guest's address of the KVM specified by * PID(pid) or hexadecimal task_struct pointer(taskp). * * EXAMPLE * Translate guest(pid:191579)'s physical address 34de5840: * * crash> qemu-vtop -g 191579 -p 34de5840 * GUEST PHYSICAL HOST VIRTUAL HOST PHYSICAL * 34de5840 7f060cbe5840 611de5840 * * PML: 72dbb67f0 => 875afa067 * PUD: 875afa0c0 => 736871067 * PMD: 736871328 => 8000000611c000e7 * PAGE: 611c00000 (2MB) * * PTE PHYSICAL FLAGS * 8000000611c000e7 611c00000 (PRESENT|RW|USER|ACCESSED|DIRTY|PSE|NX) * * VMA START END FLAGS FILE * ffff8806edca49e0 7f05d7e00000 7f0697e00000 80120073 * * PAGE PHYSICAL MAPPING INDEX CNT FLAGS * ffffea00153e8a18 611de5000 0 7f9e743e5 0 c0000000008000 * */ char *help_qemu_vtop[] = { "qemu-vtop", /* command name */ "KVM guest's address to host's address", /* short description */ "-g [pid | taskp] -p address ...", /* argument synopsis */ " This command translates a guest's address on a KVM to its host's physical", " address. It first gets the host's virtual address related to the guest's", " address, then display the information of \"vtop \".", " ", " -p address The address is a physical address of a guest", " -g [pid | taskp] Translate the guest's address of the KVM specified by", " PID(pid) or hexadecimal task_struct pointer(taskp).", "\nEXAMPLE", " Translate guest(pid:191579)'s physical address 34de5840:", " ", " crash> qemu-vtop -g 191579 -p 34de5840", " GUEST PHYSICAL HOST VIRTUAL HOST PHYSICAL", " 34de5840 7f060cbe5840 611de5840", " ", " PML: 72dbb67f0 => 875afa067", " PUD: 875afa0c0 => 736871067", " PMD: 736871328 => 8000000611c000e7", " PAGE: 611c00000 (2MB)", " ", " PTE PHYSICAL FLAGS", " 8000000611c000e7 611c00000 (PRESENT|RW|USER|ACCESSED|DIRTY|PSE|NX)", " ", " VMA START END FLAGS FILE", " ffff8806edca49e0 7f05d7e00000 7f0697e00000 80120073", " ", " PAGE PHYSICAL MAPPING INDEX CNT FLAGS", " ffffea00153e8a18 611de5000 0 7f9e743e5 0 c0000000008000", NULL }; /* * init some offsets and sizes */ static void qemu_vtop_init() { QEMU_VTOP_MEMBER_OFFSET_INIT(file_private_data, "file","private_data"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memslots, "kvm", "memslots"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memslots_nmemslots, "kvm_memslots", "nmemslots"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memslots_memslots, "kvm_memslots", "memslots"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memory_slot_base_gfn, "kvm_memory_slot", "base_gfn"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memory_slot_npages, "kvm_memory_slot", "npages"); QEMU_VTOP_MEMBER_OFFSET_INIT(kvm_memory_slot_userspace_addr, "kvm_memory_slot", "userspace_addr"); QEMU_VTOP_STRUCT_SIZE_INIT(kvm_memory_slot, "kvm_memory_slot"); } static void do_qemu_vtop_physical(ulong gpa, struct task_context *tc) { struct reference reference, *ref; ulong filep, private_data, memslots, nmemslots, hva; filep = 0; private_data = 0; memslots = 0; nmemslots = 0; ref = &reference; BZERO(ref, sizeof(struct reference)); ref->str = "anon_inode:/kvm-vm"; filep = get_file_ref(tc->task, ref); if (!filep) { error(INFO, "task(pid:%ld task:%lx) is not a qemu process\n", tc->pid, tc->task); return; } readmem(filep + QEMU_VTOP_OFFSET(file_private_data), KVADDR, &private_data, sizeof(ulong), "file private_data", FAULT_ON_ERROR); if (!private_data) { error(INFO, "failed to get information of qemu process(pid:%ld task:%lx)\n", tc->pid, tc->task); return; } readmem(private_data + QEMU_VTOP_OFFSET(kvm_memslots), KVADDR, &memslots, sizeof(ulong), "kvm memslots", FAULT_ON_ERROR); if (!memslots) { error(INFO, "failed to get information of KVM, please check " "whether KVM module is loaded\n"); return; } hva = gpa_to_hva(gpa, memslots); if (!hva) { error(INFO, "%lx has not been allocated\n", gpa); return; } print_vtop(gpa, hva, tc); } /* * get the pointer to the file struct of a file descriptor, ref->str * is used to search for the specified file descriptor. */ static ulong get_file_ref(ulong task, struct reference *ref) { ulong file = 0; open_tmpfile(); open_files_dump(task, 0, NULL); file = parse_for_file(ref->str); close_tmpfile(); return file; } static ulong parse_for_file(char *str) { char buf[BUFSIZE]; char *tokens[MAXTOKENS]; ulong file = 0; rewind(pc->tmpfile); while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (strstr(buf, str)) { parse_line(buf, tokens); file = htol(tokens[1], FAULT_ON_ERROR, NULL); break; } } return file; } static ulong gpa_to_hva(ulong gpa, ulong memslots) { ulong nmemslots, kvm_memory_slot_size, memslots_offset; ulong base_gfn_offset, npages_offset, userspace_addr_offset; ulong hva = 0; ulong gfn = gpa >> PAGE_SHIFT; readmem(memslots + QEMU_VTOP_OFFSET(kvm_memslots_nmemslots), KVADDR, &nmemslots, sizeof(ulong), "kvm_memslots nmemslots", FAULT_ON_ERROR); kvm_memory_slot_size = QEMU_VTOP_SIZE(kvm_memory_slot); memslots_offset = QEMU_VTOP_OFFSET(kvm_memslots_memslots); base_gfn_offset = QEMU_VTOP_OFFSET(kvm_memory_slot_base_gfn); npages_offset = QEMU_VTOP_OFFSET(kvm_memory_slot_npages); userspace_addr_offset = QEMU_VTOP_OFFSET(kvm_memory_slot_userspace_addr); int i = 0; int found = 0; ulong hfn; ulong base_gfn, npages, userspace_addr; /* * search every kvm_memory_slot to find which one the gfn is located in. */ while (i<=nmemslots) { readmem(memslots + memslots_offset + i*kvm_memory_slot_size + QEMU_VTOP_OFFSET(kvm_memory_slot_base_gfn), KVADDR, &base_gfn, sizeof(ulong), "kvm_memory_slot base_gfn", FAULT_ON_ERROR); readmem(memslots + memslots_offset + i*kvm_memory_slot_size + QEMU_VTOP_OFFSET(kvm_memory_slot_npages), KVADDR, &npages, sizeof(ulong), "kvm_memory_slot npages", FAULT_ON_ERROR); if (gfn>=base_gfn && gfn tmpfile); linenum = 0; glen = 16; hlen = VADDR_PRLEN; while (fgets(buf, BUFSIZE, pc->tmpfile)) { if (linenum==0) { fprintf(pc->saved_fp, "%s %s %s\n", mkstring(buf1, glen, LJUST, "GUEST PHYSICAL"), mkstring(buf2, hlen, LJUST, "HOST VIRTUAL"), mkstring(buf3, hlen, LJUST, "HOST PHYSICAL")); } else if(linenum==1) { char *host_physical; host_physical = strstr(buf, " "); while (!strncmp(host_physical, " ", 1)) { host_physical++; } fprintf(pc->saved_fp, "%*lx %*lx %s", -glen, gpa, -hlen, hva, host_physical); } else fprintf(pc->saved_fp, "%s", buf); linenum++; } close_tmpfile(); }