Created
December 12, 2017 19:12
-
-
Save DiabloHorn/d0d9745f053412ed672645e127e7301e to your computer and use it in GitHub Desktop.
Reference files to expose a QEMU guest memory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
make distclean | |
./configure | |
make subdir-x86_64-softmmu |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
HXCOMM Use DEFHEADING() to define headings in both help text and texi | |
HXCOMM Text between STEXI and ETEXI are copied to texi version and | |
HXCOMM discarded from C version | |
HXCOMM DEF(command, args, callback, arg_string, help) is used to construct | |
HXCOMM monitor commands | |
HXCOMM HXCOMM can be used for comments, discarded from both texi and C | |
STEXI | |
@table @option | |
ETEXI | |
{ | |
.name = "help|?", | |
.args_type = "name:S?", | |
.params = "[cmd]", | |
.help = "show the help", | |
.cmd = do_help_cmd, | |
}, | |
STEXI | |
@item help or ? [@var{cmd}] | |
@findex help | |
Show the help for all commands or just for command @var{cmd}. | |
ETEXI | |
{ | |
.name = "commit", | |
.args_type = "device:B", | |
.params = "device|all", | |
.help = "commit changes to the disk images (if -snapshot is used) or backing files", | |
.cmd = hmp_commit, | |
}, | |
STEXI | |
@item commit | |
@findex commit | |
Commit changes to the disk images (if -snapshot is used) or backing files. | |
If the backing file is smaller than the snapshot, then the backing file will be | |
resized to be the same size as the snapshot. If the snapshot is smaller than | |
the backing file, the backing file will not be truncated. If you want the | |
backing file to match the size of the smaller snapshot, you can safely truncate | |
it yourself once the commit operation successfully completes. | |
ETEXI | |
{ | |
.name = "q|quit", | |
.args_type = "", | |
.params = "", | |
.help = "quit the emulator", | |
.cmd = hmp_quit, | |
}, | |
STEXI | |
@item q or quit | |
@findex quit | |
Quit the emulator. | |
ETEXI | |
{ | |
.name = "block_resize", | |
.args_type = "device:B,size:o", | |
.params = "device size", | |
.help = "resize a block image", | |
.cmd = hmp_block_resize, | |
}, | |
STEXI | |
@item block_resize | |
@findex block_resize | |
Resize a block image while a guest is running. Usually requires guest | |
action to see the updated size. Resize to a lower size is supported, | |
but should be used with extreme caution. Note that this command only | |
resizes image files, it can not resize block devices like LVM volumes. | |
ETEXI | |
{ | |
.name = "block_stream", | |
.args_type = "device:B,speed:o?,base:s?", | |
.params = "device [speed [base]]", | |
.help = "copy data from a backing file into a block device", | |
.cmd = hmp_block_stream, | |
}, | |
STEXI | |
@item block_stream | |
@findex block_stream | |
Copy data from a backing file into a block device. | |
ETEXI | |
{ | |
.name = "block_job_set_speed", | |
.args_type = "device:B,speed:o", | |
.params = "device speed", | |
.help = "set maximum speed for a background block operation", | |
.cmd = hmp_block_job_set_speed, | |
}, | |
STEXI | |
@item block_job_set_speed | |
@findex block_job_set_speed | |
Set maximum speed for a background block operation. | |
ETEXI | |
{ | |
.name = "block_job_cancel", | |
.args_type = "force:-f,device:B", | |
.params = "[-f] device", | |
.help = "stop an active background block operation (use -f" | |
"\n\t\t\t if the operation is currently paused)", | |
.cmd = hmp_block_job_cancel, | |
}, | |
STEXI | |
@item block_job_cancel | |
@findex block_job_cancel | |
Stop an active background block operation (streaming, mirroring). | |
ETEXI | |
{ | |
.name = "block_job_complete", | |
.args_type = "device:B", | |
.params = "device", | |
.help = "stop an active background block operation", | |
.cmd = hmp_block_job_complete, | |
}, | |
STEXI | |
@item block_job_complete | |
@findex block_job_complete | |
Manually trigger completion of an active background block operation. | |
For mirroring, this will switch the device to the destination path. | |
ETEXI | |
{ | |
.name = "block_job_pause", | |
.args_type = "device:B", | |
.params = "device", | |
.help = "pause an active background block operation", | |
.cmd = hmp_block_job_pause, | |
}, | |
STEXI | |
@item block_job_pause | |
@findex block_job_pause | |
Pause an active block streaming operation. | |
ETEXI | |
{ | |
.name = "block_job_resume", | |
.args_type = "device:B", | |
.params = "device", | |
.help = "resume a paused background block operation", | |
.cmd = hmp_block_job_resume, | |
}, | |
STEXI | |
@item block_job_resume | |
@findex block_job_resume | |
Resume a paused block streaming operation. | |
ETEXI | |
{ | |
.name = "eject", | |
.args_type = "force:-f,device:B", | |
.params = "[-f] device", | |
.help = "eject a removable medium (use -f to force it)", | |
.cmd = hmp_eject, | |
}, | |
STEXI | |
@item eject [-f] @var{device} | |
@findex eject | |
Eject a removable medium (use -f to force it). | |
ETEXI | |
{ | |
.name = "drive_del", | |
.args_type = "id:B", | |
.params = "device", | |
.help = "remove host block device", | |
.cmd = hmp_drive_del, | |
}, | |
STEXI | |
@item drive_del @var{device} | |
@findex drive_del | |
Remove host block device. The result is that guest generated IO is no longer | |
submitted against the host device underlying the disk. Once a drive has | |
been deleted, the QEMU Block layer returns -EIO which results in IO | |
errors in the guest for applications that are reading/writing to the device. | |
These errors are always reported to the guest, regardless of the drive's error | |
actions (drive options rerror, werror). | |
ETEXI | |
{ | |
.name = "change", | |
.args_type = "device:B,target:F,arg:s?,read-only-mode:s?", | |
.params = "device filename [format [read-only-mode]]", | |
.help = "change a removable medium, optional format", | |
.cmd = hmp_change, | |
}, | |
STEXI | |
@item change @var{device} @var{setting} | |
@findex change | |
Change the configuration of a device. | |
@table @option | |
@item change @var{diskdevice} @var{filename} [@var{format} [@var{read-only-mode}]] | |
Change the medium for a removable disk device to point to @var{filename}. eg | |
@example | |
(qemu) change ide1-cd0 /path/to/some.iso | |
@end example | |
@var{format} is optional. | |
@var{read-only-mode} may be used to change the read-only status of the device. | |
It accepts the following values: | |
@table @var | |
@item retain | |
Retains the current status; this is the default. | |
@item read-only | |
Makes the device read-only. | |
@item read-write | |
Makes the device writable. | |
@end table | |
@item change vnc @var{display},@var{options} | |
Change the configuration of the VNC server. The valid syntax for @var{display} | |
and @var{options} are described at @ref{sec_invocation}. eg | |
@example | |
(qemu) change vnc localhost:1 | |
@end example | |
@item change vnc password [@var{password}] | |
Change the password associated with the VNC server. If the new password is not | |
supplied, the monitor will prompt for it to be entered. VNC passwords are only | |
significant up to 8 letters. eg | |
@example | |
(qemu) change vnc password | |
Password: ******** | |
@end example | |
@end table | |
ETEXI | |
{ | |
.name = "screendump", | |
.args_type = "filename:F", | |
.params = "filename", | |
.help = "save screen into PPM image 'filename'", | |
.cmd = hmp_screendump, | |
}, | |
STEXI | |
@item screendump @var{filename} | |
@findex screendump | |
Save screen into PPM image @var{filename}. | |
ETEXI | |
{ | |
.name = "logfile", | |
.args_type = "filename:F", | |
.params = "filename", | |
.help = "output logs to 'filename'", | |
.cmd = hmp_logfile, | |
}, | |
STEXI | |
@item logfile @var{filename} | |
@findex logfile | |
Output logs to @var{filename}. | |
ETEXI | |
{ | |
.name = "trace-event", | |
.args_type = "name:s,option:b,vcpu:i?", | |
.params = "name on|off [vcpu]", | |
.help = "changes status of a specific trace event " | |
"(vcpu: vCPU to set, default is all)", | |
.cmd = hmp_trace_event, | |
.command_completion = trace_event_completion, | |
}, | |
STEXI | |
@item trace-event | |
@findex trace-event | |
changes status of a trace event | |
ETEXI | |
#if defined(CONFIG_TRACE_SIMPLE) | |
{ | |
.name = "trace-file", | |
.args_type = "op:s?,arg:F?", | |
.params = "on|off|flush|set [arg]", | |
.help = "open, close, or flush trace file, or set a new file name", | |
.cmd = hmp_trace_file, | |
}, | |
STEXI | |
@item trace-file on|off|flush | |
@findex trace-file | |
Open, close, or flush the trace file. If no argument is given, the status of the trace file is displayed. | |
ETEXI | |
#endif | |
{ | |
.name = "log", | |
.args_type = "items:s", | |
.params = "item1[,...]", | |
.help = "activate logging of the specified items", | |
.cmd = hmp_log, | |
}, | |
STEXI | |
@item log @var{item1}[,...] | |
@findex log | |
Activate logging of the specified items. | |
ETEXI | |
{ | |
.name = "savevm", | |
.args_type = "name:s?", | |
.params = "[tag|id]", | |
.help = "save a VM snapshot. If no tag or id are provided, a new snapshot is created", | |
.cmd = hmp_savevm, | |
}, | |
STEXI | |
@item savevm [@var{tag}|@var{id}] | |
@findex savevm | |
Create a snapshot of the whole virtual machine. If @var{tag} is | |
provided, it is used as human readable identifier. If there is already | |
a snapshot with the same tag or ID, it is replaced. More info at | |
@ref{vm_snapshots}. | |
ETEXI | |
{ | |
.name = "loadvm", | |
.args_type = "name:s", | |
.params = "tag|id", | |
.help = "restore a VM snapshot from its tag or id", | |
.cmd = hmp_loadvm, | |
.command_completion = loadvm_completion, | |
}, | |
STEXI | |
@item loadvm @var{tag}|@var{id} | |
@findex loadvm | |
Set the whole virtual machine to the snapshot identified by the tag | |
@var{tag} or the unique snapshot ID @var{id}. | |
ETEXI | |
{ | |
.name = "delvm", | |
.args_type = "name:s", | |
.params = "tag|id", | |
.help = "delete a VM snapshot from its tag or id", | |
.cmd = hmp_delvm, | |
.command_completion = delvm_completion, | |
}, | |
STEXI | |
@item delvm @var{tag}|@var{id} | |
@findex delvm | |
Delete the snapshot identified by @var{tag} or @var{id}. | |
ETEXI | |
{ | |
.name = "singlestep", | |
.args_type = "option:s?", | |
.params = "[on|off]", | |
.help = "run emulation in singlestep mode or switch to normal mode", | |
.cmd = hmp_singlestep, | |
}, | |
STEXI | |
@item singlestep [off] | |
@findex singlestep | |
Run the emulation in single step mode. | |
If called with option off, the emulation returns to normal mode. | |
ETEXI | |
{ | |
.name = "stop", | |
.args_type = "", | |
.params = "", | |
.help = "stop emulation", | |
.cmd = hmp_stop, | |
}, | |
STEXI | |
@item stop | |
@findex stop | |
Stop emulation. | |
ETEXI | |
{ | |
.name = "c|cont", | |
.args_type = "", | |
.params = "", | |
.help = "resume emulation", | |
.cmd = hmp_cont, | |
}, | |
STEXI | |
@item c or cont | |
@findex cont | |
Resume emulation. | |
ETEXI | |
{ | |
.name = "system_wakeup", | |
.args_type = "", | |
.params = "", | |
.help = "wakeup guest from suspend", | |
.cmd = hmp_system_wakeup, | |
}, | |
STEXI | |
@item system_wakeup | |
@findex system_wakeup | |
Wakeup guest from suspend. | |
ETEXI | |
{ | |
.name = "gdbserver", | |
.args_type = "device:s?", | |
.params = "[device]", | |
.help = "start gdbserver on given device (default 'tcp::1234'), stop with 'none'", | |
.cmd = hmp_gdbserver, | |
}, | |
STEXI | |
@item gdbserver [@var{port}] | |
@findex gdbserver | |
Start gdbserver session (default @var{port}=1234) | |
ETEXI | |
{ | |
.name = "x", | |
.args_type = "fmt:/,addr:l", | |
.params = "/fmt addr", | |
.help = "virtual memory dump starting at 'addr'", | |
.cmd = hmp_memory_dump, | |
}, | |
STEXI | |
@item x/fmt @var{addr} | |
@findex x | |
Virtual memory dump starting at @var{addr}. | |
ETEXI | |
{ | |
.name = "xp", | |
.args_type = "fmt:/,addr:l", | |
.params = "/fmt addr", | |
.help = "physical memory dump starting at 'addr'", | |
.cmd = hmp_physical_memory_dump, | |
}, | |
STEXI | |
@item xp /@var{fmt} @var{addr} | |
@findex xp | |
Physical memory dump starting at @var{addr}. | |
@var{fmt} is a format which tells the command how to format the | |
data. Its syntax is: @option{/@{count@}@{format@}@{size@}} | |
@table @var | |
@item count | |
is the number of items to be dumped. | |
@item format | |
can be x (hex), d (signed decimal), u (unsigned decimal), o (octal), | |
c (char) or i (asm instruction). | |
@item size | |
can be b (8 bits), h (16 bits), w (32 bits) or g (64 bits). On x86, | |
@code{h} or @code{w} can be specified with the @code{i} format to | |
respectively select 16 or 32 bit code instruction size. | |
@end table | |
Examples: | |
@itemize | |
@item | |
Dump 10 instructions at the current instruction pointer: | |
@example | |
(qemu) x/10i $eip | |
0x90107063: ret | |
0x90107064: sti | |
0x90107065: lea 0x0(%esi,1),%esi | |
0x90107069: lea 0x0(%edi,1),%edi | |
0x90107070: ret | |
0x90107071: jmp 0x90107080 | |
0x90107073: nop | |
0x90107074: nop | |
0x90107075: nop | |
0x90107076: nop | |
@end example | |
@item | |
Dump 80 16 bit values at the start of the video memory. | |
@smallexample | |
(qemu) xp/80hx 0xb8000 | |
0x000b8000: 0x0b50 0x0b6c 0x0b65 0x0b78 0x0b38 0x0b36 0x0b2f 0x0b42 | |
0x000b8010: 0x0b6f 0x0b63 0x0b68 0x0b73 0x0b20 0x0b56 0x0b47 0x0b41 | |
0x000b8020: 0x0b42 0x0b69 0x0b6f 0x0b73 0x0b20 0x0b63 0x0b75 0x0b72 | |
0x000b8030: 0x0b72 0x0b65 0x0b6e 0x0b74 0x0b2d 0x0b63 0x0b76 0x0b73 | |
0x000b8040: 0x0b20 0x0b30 0x0b35 0x0b20 0x0b4e 0x0b6f 0x0b76 0x0b20 | |
0x000b8050: 0x0b32 0x0b30 0x0b30 0x0b33 0x0720 0x0720 0x0720 0x0720 | |
0x000b8060: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 | |
0x000b8070: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 | |
0x000b8080: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 | |
0x000b8090: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 | |
@end smallexample | |
@end itemize | |
ETEXI | |
{ | |
.name = "gpa2hva", | |
.args_type = "addr:l", | |
.params = "addr", | |
.help = "print the host virtual address corresponding to a guest physical address", | |
.cmd = hmp_gpa2hva, | |
}, | |
STEXI | |
@item gpa2hva @var{addr} | |
@findex gpa2hva | |
Print the host virtual address at which the guest's physical address @var{addr} | |
is mapped. | |
ETEXI | |
#ifdef CONFIG_LINUX | |
{ | |
.name = "gpa2hpa", | |
.args_type = "addr:l", | |
.params = "addr", | |
.help = "print the host physical address corresponding to a guest physical address", | |
.cmd = hmp_gpa2hpa, | |
}, | |
#endif | |
STEXI | |
@item gpa2hpa @var{addr} | |
@findex gpa2hpa | |
Print the host physical address at which the guest's physical address @var{addr} | |
is mapped. | |
ETEXI | |
{ | |
.name = "p|print", | |
.args_type = "fmt:/,val:l", | |
.params = "/fmt expr", | |
.help = "print expression value (use $reg for CPU register access)", | |
.cmd = do_print, | |
}, | |
STEXI | |
@item p or print/@var{fmt} @var{expr} | |
@findex print | |
Print expression value. Only the @var{format} part of @var{fmt} is | |
used. | |
ETEXI | |
{ | |
.name = "i", | |
.args_type = "fmt:/,addr:i,index:i.", | |
.params = "/fmt addr", | |
.help = "I/O port read", | |
.cmd = hmp_ioport_read, | |
}, | |
STEXI | |
@item i/@var{fmt} @var{addr} [.@var{index}] | |
@findex i | |
Read I/O port. | |
ETEXI | |
{ | |
.name = "o", | |
.args_type = "fmt:/,addr:i,val:i", | |
.params = "/fmt addr value", | |
.help = "I/O port write", | |
.cmd = hmp_ioport_write, | |
}, | |
STEXI | |
@item o/@var{fmt} @var{addr} @var{val} | |
@findex o | |
Write to I/O port. | |
ETEXI | |
{ | |
.name = "sendkey", | |
.args_type = "keys:s,hold-time:i?", | |
.params = "keys [hold_ms]", | |
.help = "send keys to the VM (e.g. 'sendkey ctrl-alt-f1', default hold time=100 ms)", | |
.cmd = hmp_sendkey, | |
.command_completion = sendkey_completion, | |
}, | |
STEXI | |
@item sendkey @var{keys} | |
@findex sendkey | |
Send @var{keys} to the guest. @var{keys} could be the name of the | |
key or the raw value in hexadecimal format. Use @code{-} to press | |
several keys simultaneously. Example: | |
@example | |
sendkey ctrl-alt-f1 | |
@end example | |
This command is useful to send keys that your graphical user interface | |
intercepts at low level, such as @code{ctrl-alt-f1} in X Window. | |
ETEXI | |
{ | |
.name = "system_reset", | |
.args_type = "", | |
.params = "", | |
.help = "reset the system", | |
.cmd = hmp_system_reset, | |
}, | |
STEXI | |
@item system_reset | |
@findex system_reset | |
Reset the system. | |
ETEXI | |
{ | |
.name = "system_powerdown", | |
.args_type = "", | |
.params = "", | |
.help = "send system power down event", | |
.cmd = hmp_system_powerdown, | |
}, | |
STEXI | |
@item system_powerdown | |
@findex system_powerdown | |
Power down the system (if supported). | |
ETEXI | |
{ | |
.name = "sum", | |
.args_type = "start:i,size:i", | |
.params = "addr size", | |
.help = "compute the checksum of a memory region", | |
.cmd = hmp_sum, | |
}, | |
STEXI | |
@item sum @var{addr} @var{size} | |
@findex sum | |
Compute the checksum of a memory region. | |
ETEXI | |
{ | |
.name = "usb_add", | |
.args_type = "devname:s", | |
.params = "device", | |
.help = "add USB device (e.g. 'host:bus.addr' or 'host:vendor_id:product_id')", | |
.cmd = hmp_usb_add, | |
}, | |
STEXI | |
@item usb_add @var{devname} | |
@findex usb_add | |
Add the USB device @var{devname}. This command is deprecated, please | |
use @code{device_add} instead. For details of available devices see | |
@ref{usb_devices} | |
ETEXI | |
{ | |
.name = "usb_del", | |
.args_type = "devname:s", | |
.params = "device", | |
.help = "remove USB device 'bus.addr'", | |
.cmd = hmp_usb_del, | |
}, | |
STEXI | |
@item usb_del @var{devname} | |
@findex usb_del | |
Remove the USB device @var{devname} from the QEMU virtual USB | |
hub. @var{devname} has the syntax @code{bus.addr}. Use the monitor | |
command @code{info usb} to see the devices you can remove. This | |
command is deprecated, please use @code{device_del} instead. | |
ETEXI | |
{ | |
.name = "device_add", | |
.args_type = "device:O", | |
.params = "driver[,prop=value][,...]", | |
.help = "add device, like -device on the command line", | |
.cmd = hmp_device_add, | |
.command_completion = device_add_completion, | |
}, | |
STEXI | |
@item device_add @var{config} | |
@findex device_add | |
Add device. | |
ETEXI | |
{ | |
.name = "device_del", | |
.args_type = "id:s", | |
.params = "device", | |
.help = "remove device", | |
.cmd = hmp_device_del, | |
.command_completion = device_del_completion, | |
}, | |
STEXI | |
@item device_del @var{id} | |
@findex device_del | |
Remove device @var{id}. @var{id} may be a short ID | |
or a QOM object path. | |
ETEXI | |
{ | |
.name = "cpu", | |
.args_type = "index:i", | |
.params = "index", | |
.help = "set the default CPU", | |
.cmd = hmp_cpu, | |
}, | |
STEXI | |
@item cpu @var{index} | |
@findex cpu | |
Set the default CPU. | |
ETEXI | |
{ | |
.name = "mouse_move", | |
.args_type = "dx_str:s,dy_str:s,dz_str:s?", | |
.params = "dx dy [dz]", | |
.help = "send mouse move events", | |
.cmd = hmp_mouse_move, | |
}, | |
STEXI | |
@item mouse_move @var{dx} @var{dy} [@var{dz}] | |
@findex mouse_move | |
Move the active mouse to the specified coordinates @var{dx} @var{dy} | |
with optional scroll axis @var{dz}. | |
ETEXI | |
{ | |
.name = "mouse_button", | |
.args_type = "button_state:i", | |
.params = "state", | |
.help = "change mouse button state (1=L, 2=M, 4=R)", | |
.cmd = hmp_mouse_button, | |
}, | |
STEXI | |
@item mouse_button @var{val} | |
@findex mouse_button | |
Change the active mouse button state @var{val} (1=L, 2=M, 4=R). | |
ETEXI | |
{ | |
.name = "mouse_set", | |
.args_type = "index:i", | |
.params = "index", | |
.help = "set which mouse device receives events", | |
.cmd = hmp_mouse_set, | |
}, | |
STEXI | |
@item mouse_set @var{index} | |
@findex mouse_set | |
Set which mouse device receives events at given @var{index}, index | |
can be obtained with | |
@example | |
info mice | |
@end example | |
ETEXI | |
{ | |
.name = "wavcapture", | |
.args_type = "path:F,freq:i?,bits:i?,nchannels:i?", | |
.params = "path [frequency [bits [channels]]]", | |
.help = "capture audio to a wave file (default frequency=44100 bits=16 channels=2)", | |
.cmd = hmp_wavcapture, | |
}, | |
STEXI | |
@item wavcapture @var{filename} [@var{frequency} [@var{bits} [@var{channels}]]] | |
@findex wavcapture | |
Capture audio into @var{filename}. Using sample rate @var{frequency} | |
bits per sample @var{bits} and number of channels @var{channels}. | |
Defaults: | |
@itemize @minus | |
@item Sample rate = 44100 Hz - CD quality | |
@item Bits = 16 | |
@item Number of channels = 2 - Stereo | |
@end itemize | |
ETEXI | |
{ | |
.name = "stopcapture", | |
.args_type = "n:i", | |
.params = "capture index", | |
.help = "stop capture", | |
.cmd = hmp_stopcapture, | |
}, | |
STEXI | |
@item stopcapture @var{index} | |
@findex stopcapture | |
Stop capture with a given @var{index}, index can be obtained with | |
@example | |
info capture | |
@end example | |
ETEXI | |
{ | |
.name = "memsave", | |
.args_type = "val:l,size:i,filename:s", | |
.params = "addr size file", | |
.help = "save to disk virtual memory dump starting at 'addr' of size 'size'", | |
.cmd = hmp_memsave, | |
}, | |
STEXI | |
@item memsave @var{addr} @var{size} @var{file} | |
@findex memsave | |
save to disk virtual memory dump starting at @var{addr} of size @var{size}. | |
ETEXI | |
{ | |
.name = "pmemsave", | |
.args_type = "val:l,size:i,filename:s", | |
.params = "addr size file", | |
.help = "save to disk physical memory dump starting at 'addr' of size 'size'", | |
.cmd = hmp_pmemsave, | |
}, | |
STEXI | |
@item pmemsave @var{addr} @var{size} @var{file} | |
@findex pmemsave | |
save to disk physical memory dump starting at @var{addr} of size @var{size}. | |
ETEXI | |
{ | |
.name = "pmemaccess", | |
.args_type = "path:s", | |
.params = "path", | |
.help = "mount guest physical memory image at 'path'", | |
.cmd = hmp_physical_memory_access, | |
}, | |
STEXI | |
@item pmemaccess @var{path} | |
@findex pmemaccess | |
mount guest physical memory image at @var{path}. | |
ETEXI | |
{ | |
.name = "boot_set", | |
.args_type = "bootdevice:s", | |
.params = "bootdevice", | |
.help = "define new values for the boot device list", | |
.cmd = hmp_boot_set, | |
}, | |
STEXI | |
@item boot_set @var{bootdevicelist} | |
@findex boot_set | |
Define new values for the boot device list. Those values will override | |
the values specified on the command line through the @code{-boot} option. | |
The values that can be specified here depend on the machine type, but are | |
the same that can be specified in the @code{-boot} command line option. | |
ETEXI | |
{ | |
.name = "nmi", | |
.args_type = "", | |
.params = "", | |
.help = "inject an NMI", | |
.cmd = hmp_nmi, | |
}, | |
STEXI | |
@item nmi @var{cpu} | |
@findex nmi | |
Inject an NMI on the default CPU (x86/s390) or all CPUs (ppc64). | |
ETEXI | |
{ | |
.name = "ringbuf_write", | |
.args_type = "device:s,data:s", | |
.params = "device data", | |
.help = "Write to a ring buffer character device", | |
.cmd = hmp_ringbuf_write, | |
.command_completion = ringbuf_write_completion, | |
}, | |
STEXI | |
@item ringbuf_write @var{device} @var{data} | |
@findex ringbuf_write | |
Write @var{data} to ring buffer character device @var{device}. | |
@var{data} must be a UTF-8 string. | |
ETEXI | |
{ | |
.name = "ringbuf_read", | |
.args_type = "device:s,size:i", | |
.params = "device size", | |
.help = "Read from a ring buffer character device", | |
.cmd = hmp_ringbuf_read, | |
.command_completion = ringbuf_write_completion, | |
}, | |
STEXI | |
@item ringbuf_read @var{device} | |
@findex ringbuf_read | |
Read and print up to @var{size} bytes from ring buffer character | |
device @var{device}. | |
Certain non-printable characters are printed \uXXXX, where XXXX is the | |
character code in hexadecimal. Character \ is printed \\. | |
Bug: can screw up when the buffer contains invalid UTF-8 sequences, | |
NUL characters, after the ring buffer lost data, and when reading | |
stops because the size limit is reached. | |
ETEXI | |
{ | |
.name = "migrate", | |
.args_type = "detach:-d,blk:-b,inc:-i,uri:s", | |
.params = "[-d] [-b] [-i] uri", | |
.help = "migrate to URI (using -d to not wait for completion)" | |
"\n\t\t\t -b for migration without shared storage with" | |
" full copy of disk\n\t\t\t -i for migration without " | |
"shared storage with incremental copy of disk " | |
"(base image shared between src and destination)", | |
.cmd = hmp_migrate, | |
}, | |
STEXI | |
@item migrate [-d] [-b] [-i] @var{uri} | |
@findex migrate | |
Migrate to @var{uri} (using -d to not wait for completion). | |
-b for migration with full copy of disk | |
-i for migration with incremental copy of disk (base image is shared) | |
ETEXI | |
{ | |
.name = "migrate_cancel", | |
.args_type = "", | |
.params = "", | |
.help = "cancel the current VM migration", | |
.cmd = hmp_migrate_cancel, | |
}, | |
STEXI | |
@item migrate_cancel | |
@findex migrate_cancel | |
Cancel the current VM migration. | |
ETEXI | |
{ | |
.name = "migrate_continue", | |
.args_type = "state:s", | |
.params = "state", | |
.help = "Continue migration from the given paused state", | |
.cmd = hmp_migrate_continue, | |
}, | |
STEXI | |
@item migrate_continue @var{state} | |
@findex migrate_continue | |
Continue migration from the paused state @var{state} | |
ETEXI | |
{ | |
.name = "migrate_incoming", | |
.args_type = "uri:s", | |
.params = "uri", | |
.help = "Continue an incoming migration from an -incoming defer", | |
.cmd = hmp_migrate_incoming, | |
}, | |
STEXI | |
@item migrate_incoming @var{uri} | |
@findex migrate_incoming | |
Continue an incoming migration using the @var{uri} (that has the same syntax | |
as the -incoming option). | |
ETEXI | |
{ | |
.name = "migrate_set_cache_size", | |
.args_type = "value:o", | |
.params = "value", | |
.help = "set cache size (in bytes) for XBZRLE migrations," | |
"the cache size will be rounded down to the nearest " | |
"power of 2.\n" | |
"The cache size affects the number of cache misses." | |
"In case of a high cache miss ratio you need to increase" | |
" the cache size", | |
.cmd = hmp_migrate_set_cache_size, | |
}, | |
STEXI | |
@item migrate_set_cache_size @var{value} | |
@findex migrate_set_cache_size | |
Set cache size to @var{value} (in bytes) for xbzrle migrations. | |
ETEXI | |
{ | |
.name = "migrate_set_speed", | |
.args_type = "value:o", | |
.params = "value", | |
.help = "set maximum speed (in bytes) for migrations. " | |
"Defaults to MB if no size suffix is specified, ie. B/K/M/G/T", | |
.cmd = hmp_migrate_set_speed, | |
}, | |
STEXI | |
@item migrate_set_speed @var{value} | |
@findex migrate_set_speed | |
Set maximum speed to @var{value} (in bytes) for migrations. | |
ETEXI | |
{ | |
.name = "migrate_set_downtime", | |
.args_type = "value:T", | |
.params = "value", | |
.help = "set maximum tolerated downtime (in seconds) for migrations", | |
.cmd = hmp_migrate_set_downtime, | |
}, | |
STEXI | |
@item migrate_set_downtime @var{second} | |
@findex migrate_set_downtime | |
Set maximum tolerated downtime (in seconds) for migration. | |
ETEXI | |
{ | |
.name = "migrate_set_capability", | |
.args_type = "capability:s,state:b", | |
.params = "capability state", | |
.help = "Enable/Disable the usage of a capability for migration", | |
.cmd = hmp_migrate_set_capability, | |
.command_completion = migrate_set_capability_completion, | |
}, | |
STEXI | |
@item migrate_set_capability @var{capability} @var{state} | |
@findex migrate_set_capability | |
Enable/Disable the usage of a capability @var{capability} for migration. | |
ETEXI | |
{ | |
.name = "migrate_set_parameter", | |
.args_type = "parameter:s,value:s", | |
.params = "parameter value", | |
.help = "Set the parameter for migration", | |
.cmd = hmp_migrate_set_parameter, | |
.command_completion = migrate_set_parameter_completion, | |
}, | |
STEXI | |
@item migrate_set_parameter @var{parameter} @var{value} | |
@findex migrate_set_parameter | |
Set the parameter @var{parameter} for migration. | |
ETEXI | |
{ | |
.name = "migrate_start_postcopy", | |
.args_type = "", | |
.params = "", | |
.help = "Followup to a migration command to switch the migration" | |
" to postcopy mode. The postcopy-ram capability must " | |
"be set before the original migration command.", | |
.cmd = hmp_migrate_start_postcopy, | |
}, | |
STEXI | |
@item migrate_start_postcopy | |
@findex migrate_start_postcopy | |
Switch in-progress migration to postcopy mode. Ignored after the end of | |
migration (or once already in postcopy). | |
ETEXI | |
{ | |
.name = "x_colo_lost_heartbeat", | |
.args_type = "", | |
.params = "", | |
.help = "Tell COLO that heartbeat is lost,\n\t\t\t" | |
"a failover or takeover is needed.", | |
.cmd = hmp_x_colo_lost_heartbeat, | |
}, | |
STEXI | |
@item x_colo_lost_heartbeat | |
@findex x_colo_lost_heartbeat | |
Tell COLO that heartbeat is lost, a failover or takeover is needed. | |
ETEXI | |
{ | |
.name = "client_migrate_info", | |
.args_type = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?", | |
.params = "protocol hostname port tls-port cert-subject", | |
.help = "set migration information for remote display", | |
.cmd = hmp_client_migrate_info, | |
}, | |
STEXI | |
@item client_migrate_info @var{protocol} @var{hostname} @var{port} @var{tls-port} @var{cert-subject} | |
@findex client_migrate_info | |
Set migration information for remote display. This makes the server | |
ask the client to automatically reconnect using the new parameters | |
once migration finished successfully. Only implemented for SPICE. | |
ETEXI | |
{ | |
.name = "dump-guest-memory", | |
.args_type = "paging:-p,detach:-d,zlib:-z,lzo:-l,snappy:-s,filename:F,begin:i?,length:i?", | |
.params = "[-p] [-d] [-z|-l|-s] filename [begin length]", | |
.help = "dump guest memory into file 'filename'.\n\t\t\t" | |
"-p: do paging to get guest's memory mapping.\n\t\t\t" | |
"-d: return immediately (do not wait for completion).\n\t\t\t" | |
"-z: dump in kdump-compressed format, with zlib compression.\n\t\t\t" | |
"-l: dump in kdump-compressed format, with lzo compression.\n\t\t\t" | |
"-s: dump in kdump-compressed format, with snappy compression.\n\t\t\t" | |
"begin: the starting physical address.\n\t\t\t" | |
"length: the memory size, in bytes.", | |
.cmd = hmp_dump_guest_memory, | |
}, | |
STEXI | |
@item dump-guest-memory [-p] @var{filename} @var{begin} @var{length} | |
@item dump-guest-memory [-z|-l|-s] @var{filename} | |
@findex dump-guest-memory | |
Dump guest memory to @var{protocol}. The file can be processed with crash or | |
gdb. Without -z|-l|-s, the dump format is ELF. | |
-p: do paging to get guest's memory mapping. | |
-z: dump in kdump-compressed format, with zlib compression. | |
-l: dump in kdump-compressed format, with lzo compression. | |
-s: dump in kdump-compressed format, with snappy compression. | |
filename: dump file name. | |
begin: the starting physical address. It's optional, and should be | |
specified together with length. | |
length: the memory size, in bytes. It's optional, and should be specified | |
together with begin. | |
ETEXI | |
#if defined(TARGET_S390X) | |
{ | |
.name = "dump-skeys", | |
.args_type = "filename:F", | |
.params = "", | |
.help = "Save guest storage keys into file 'filename'.\n", | |
.cmd = hmp_dump_skeys, | |
}, | |
#endif | |
STEXI | |
@item dump-skeys @var{filename} | |
@findex dump-skeys | |
Save guest storage keys to a file. | |
ETEXI | |
#if defined(TARGET_S390X) | |
{ | |
.name = "migration_mode", | |
.args_type = "mode:i", | |
.params = "mode", | |
.help = "Enables or disables migration mode\n", | |
.cmd = hmp_migrationmode, | |
}, | |
#endif | |
STEXI | |
@item migration_mode @var{mode} | |
@findex migration_mode | |
Enables or disables migration mode. | |
ETEXI | |
{ | |
.name = "snapshot_blkdev", | |
.args_type = "reuse:-n,device:B,snapshot-file:s?,format:s?", | |
.params = "[-n] device [new-image-file] [format]", | |
.help = "initiates a live snapshot\n\t\t\t" | |
"of device. If a new image file is specified, the\n\t\t\t" | |
"new image file will become the new root image.\n\t\t\t" | |
"If format is specified, the snapshot file will\n\t\t\t" | |
"be created in that format.\n\t\t\t" | |
"The default format is qcow2. The -n flag requests QEMU\n\t\t\t" | |
"to reuse the image found in new-image-file, instead of\n\t\t\t" | |
"recreating it from scratch.", | |
.cmd = hmp_snapshot_blkdev, | |
}, | |
STEXI | |
@item snapshot_blkdev | |
@findex snapshot_blkdev | |
Snapshot device, using snapshot file as target if provided | |
ETEXI | |
{ | |
.name = "snapshot_blkdev_internal", | |
.args_type = "device:B,name:s", | |
.params = "device name", | |
.help = "take an internal snapshot of device.\n\t\t\t" | |
"The format of the image used by device must\n\t\t\t" | |
"support it, such as qcow2.\n\t\t\t", | |
.cmd = hmp_snapshot_blkdev_internal, | |
}, | |
STEXI | |
@item snapshot_blkdev_internal | |
@findex snapshot_blkdev_internal | |
Take an internal snapshot on device if it support | |
ETEXI | |
{ | |
.name = "snapshot_delete_blkdev_internal", | |
.args_type = "device:B,name:s,id:s?", | |
.params = "device name [id]", | |
.help = "delete an internal snapshot of device.\n\t\t\t" | |
"If id is specified, qemu will try delete\n\t\t\t" | |
"the snapshot matching both id and name.\n\t\t\t" | |
"The format of the image used by device must\n\t\t\t" | |
"support it, such as qcow2.\n\t\t\t", | |
.cmd = hmp_snapshot_delete_blkdev_internal, | |
}, | |
STEXI | |
@item snapshot_delete_blkdev_internal | |
@findex snapshot_delete_blkdev_internal | |
Delete an internal snapshot on device if it support | |
ETEXI | |
{ | |
.name = "drive_mirror", | |
.args_type = "reuse:-n,full:-f,device:B,target:s,format:s?", | |
.params = "[-n] [-f] device target [format]", | |
.help = "initiates live storage\n\t\t\t" | |
"migration for a device. The device's contents are\n\t\t\t" | |
"copied to the new image file, including data that\n\t\t\t" | |
"is written after the command is started.\n\t\t\t" | |
"The -n flag requests QEMU to reuse the image found\n\t\t\t" | |
"in new-image-file, instead of recreating it from scratch.\n\t\t\t" | |
"The -f flag requests QEMU to copy the whole disk,\n\t\t\t" | |
"so that the result does not need a backing file.\n\t\t\t", | |
.cmd = hmp_drive_mirror, | |
}, | |
STEXI | |
@item drive_mirror | |
@findex drive_mirror | |
Start mirroring a block device's writes to a new destination, | |
using the specified target. | |
ETEXI | |
{ | |
.name = "drive_backup", | |
.args_type = "reuse:-n,full:-f,compress:-c,device:B,target:s,format:s?", | |
.params = "[-n] [-f] [-c] device target [format]", | |
.help = "initiates a point-in-time\n\t\t\t" | |
"copy for a device. The device's contents are\n\t\t\t" | |
"copied to the new image file, excluding data that\n\t\t\t" | |
"is written after the command is started.\n\t\t\t" | |
"The -n flag requests QEMU to reuse the image found\n\t\t\t" | |
"in new-image-file, instead of recreating it from scratch.\n\t\t\t" | |
"The -f flag requests QEMU to copy the whole disk,\n\t\t\t" | |
"so that the result does not need a backing file.\n\t\t\t" | |
"The -c flag requests QEMU to compress backup data\n\t\t\t" | |
"(if the target format supports it).\n\t\t\t", | |
.cmd = hmp_drive_backup, | |
}, | |
STEXI | |
@item drive_backup | |
@findex drive_backup | |
Start a point-in-time copy of a block device to a specificed target. | |
ETEXI | |
{ | |
.name = "drive_add", | |
.args_type = "node:-n,pci_addr:s,opts:s", | |
.params = "[-n] [[<domain>:]<bus>:]<slot>\n" | |
"[file=file][,if=type][,bus=n]\n" | |
"[,unit=m][,media=d][,index=i]\n" | |
"[,cyls=c,heads=h,secs=s[,trans=t]]\n" | |
"[,snapshot=on|off][,cache=on|off]\n" | |
"[,readonly=on|off][,copy-on-read=on|off]", | |
.help = "add drive to PCI storage controller", | |
.cmd = hmp_drive_add, | |
}, | |
STEXI | |
@item drive_add | |
@findex drive_add | |
Add drive to PCI storage controller. | |
ETEXI | |
{ | |
.name = "pcie_aer_inject_error", | |
.args_type = "advisory_non_fatal:-a,correctable:-c," | |
"id:s,error_status:s," | |
"header0:i?,header1:i?,header2:i?,header3:i?," | |
"prefix0:i?,prefix1:i?,prefix2:i?,prefix3:i?", | |
.params = "[-a] [-c] id " | |
"<error_status> [<tlp header> [<tlp header prefix>]]", | |
.help = "inject pcie aer error\n\t\t\t" | |
" -a for advisory non fatal error\n\t\t\t" | |
" -c for correctable error\n\t\t\t" | |
"<id> = qdev device id\n\t\t\t" | |
"<error_status> = error string or 32bit\n\t\t\t" | |
"<tlb header> = 32bit x 4\n\t\t\t" | |
"<tlb header prefix> = 32bit x 4", | |
.cmd = hmp_pcie_aer_inject_error, | |
}, | |
STEXI | |
@item pcie_aer_inject_error | |
@findex pcie_aer_inject_error | |
Inject PCIe AER error | |
ETEXI | |
{ | |
.name = "host_net_add", | |
.args_type = "device:s,opts:s?", | |
.params = "tap|user|socket|vde|netmap|bridge|vhost-user|dump [options]", | |
.help = "add host VLAN client (deprecated, use netdev_add instead)", | |
.cmd = hmp_host_net_add, | |
.command_completion = host_net_add_completion, | |
}, | |
STEXI | |
@item host_net_add | |
@findex host_net_add | |
Add host VLAN client. Deprecated, please use @code{netdev_add} instead. | |
ETEXI | |
{ | |
.name = "host_net_remove", | |
.args_type = "vlan_id:i,device:s", | |
.params = "vlan_id name", | |
.help = "remove host VLAN client (deprecated, use netdev_del instead)", | |
.cmd = hmp_host_net_remove, | |
.command_completion = host_net_remove_completion, | |
}, | |
STEXI | |
@item host_net_remove | |
@findex host_net_remove | |
Remove host VLAN client. Deprecated, please use @code{netdev_del} instead. | |
ETEXI | |
{ | |
.name = "netdev_add", | |
.args_type = "netdev:O", | |
.params = "[user|tap|socket|vde|bridge|hubport|netmap|vhost-user],id=str[,prop=value][,...]", | |
.help = "add host network device", | |
.cmd = hmp_netdev_add, | |
.command_completion = netdev_add_completion, | |
}, | |
STEXI | |
@item netdev_add | |
@findex netdev_add | |
Add host network device. | |
ETEXI | |
{ | |
.name = "netdev_del", | |
.args_type = "id:s", | |
.params = "id", | |
.help = "remove host network device", | |
.cmd = hmp_netdev_del, | |
.command_completion = netdev_del_completion, | |
}, | |
STEXI | |
@item netdev_del | |
@findex netdev_del | |
Remove host network device. | |
ETEXI | |
{ | |
.name = "object_add", | |
.args_type = "object:O", | |
.params = "[qom-type=]type,id=str[,prop=value][,...]", | |
.help = "create QOM object", | |
.cmd = hmp_object_add, | |
.command_completion = object_add_completion, | |
}, | |
STEXI | |
@item object_add | |
@findex object_add | |
Create QOM object. | |
ETEXI | |
{ | |
.name = "object_del", | |
.args_type = "id:s", | |
.params = "id", | |
.help = "destroy QOM object", | |
.cmd = hmp_object_del, | |
.command_completion = object_del_completion, | |
}, | |
STEXI | |
@item object_del | |
@findex object_del | |
Destroy QOM object. | |
ETEXI | |
#ifdef CONFIG_SLIRP | |
{ | |
.name = "hostfwd_add", | |
.args_type = "arg1:s,arg2:s?,arg3:s?", | |
.params = "[vlan_id name] [tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport", | |
.help = "redirect TCP or UDP connections from host to guest (requires -net user)", | |
.cmd = hmp_hostfwd_add, | |
}, | |
#endif | |
STEXI | |
@item hostfwd_add | |
@findex hostfwd_add | |
Redirect TCP or UDP connections from host to guest (requires -net user). | |
ETEXI | |
#ifdef CONFIG_SLIRP | |
{ | |
.name = "hostfwd_remove", | |
.args_type = "arg1:s,arg2:s?,arg3:s?", | |
.params = "[vlan_id name] [tcp|udp]:[hostaddr]:hostport", | |
.help = "remove host-to-guest TCP or UDP redirection", | |
.cmd = hmp_hostfwd_remove, | |
}, | |
#endif | |
STEXI | |
@item hostfwd_remove | |
@findex hostfwd_remove | |
Remove host-to-guest TCP or UDP redirection. | |
ETEXI | |
{ | |
.name = "balloon", | |
.args_type = "value:M", | |
.params = "target", | |
.help = "request VM to change its memory allocation (in MB)", | |
.cmd = hmp_balloon, | |
}, | |
STEXI | |
@item balloon @var{value} | |
@findex balloon | |
Request VM to change its memory allocation to @var{value} (in MB). | |
ETEXI | |
{ | |
.name = "set_link", | |
.args_type = "name:s,up:b", | |
.params = "name on|off", | |
.help = "change the link status of a network adapter", | |
.cmd = hmp_set_link, | |
.command_completion = set_link_completion, | |
}, | |
STEXI | |
@item set_link @var{name} [on|off] | |
@findex set_link | |
Switch link @var{name} on (i.e. up) or off (i.e. down). | |
ETEXI | |
{ | |
.name = "watchdog_action", | |
.args_type = "action:s", | |
.params = "[reset|shutdown|poweroff|pause|debug|none]", | |
.help = "change watchdog action", | |
.cmd = hmp_watchdog_action, | |
.command_completion = watchdog_action_completion, | |
}, | |
STEXI | |
@item watchdog_action | |
@findex watchdog_action | |
Change watchdog action. | |
ETEXI | |
{ | |
.name = "acl_show", | |
.args_type = "aclname:s", | |
.params = "aclname", | |
.help = "list rules in the access control list", | |
.cmd = hmp_acl_show, | |
}, | |
STEXI | |
@item acl_show @var{aclname} | |
@findex acl_show | |
List all the matching rules in the access control list, and the default | |
policy. There are currently two named access control lists, | |
@var{vnc.x509dname} and @var{vnc.username} matching on the x509 client | |
certificate distinguished name, and SASL username respectively. | |
ETEXI | |
{ | |
.name = "acl_policy", | |
.args_type = "aclname:s,policy:s", | |
.params = "aclname allow|deny", | |
.help = "set default access control list policy", | |
.cmd = hmp_acl_policy, | |
}, | |
STEXI | |
@item acl_policy @var{aclname} @code{allow|deny} | |
@findex acl_policy | |
Set the default access control list policy, used in the event that | |
none of the explicit rules match. The default policy at startup is | |
always @code{deny}. | |
ETEXI | |
{ | |
.name = "acl_add", | |
.args_type = "aclname:s,match:s,policy:s,index:i?", | |
.params = "aclname match allow|deny [index]", | |
.help = "add a match rule to the access control list", | |
.cmd = hmp_acl_add, | |
}, | |
STEXI | |
@item acl_add @var{aclname} @var{match} @code{allow|deny} [@var{index}] | |
@findex acl_add | |
Add a match rule to the access control list, allowing or denying access. | |
The match will normally be an exact username or x509 distinguished name, | |
but can optionally include wildcard globs. eg @code{*@@EXAMPLE.COM} to | |
allow all users in the @code{EXAMPLE.COM} kerberos realm. The match will | |
normally be appended to the end of the ACL, but can be inserted | |
earlier in the list if the optional @var{index} parameter is supplied. | |
ETEXI | |
{ | |
.name = "acl_remove", | |
.args_type = "aclname:s,match:s", | |
.params = "aclname match", | |
.help = "remove a match rule from the access control list", | |
.cmd = hmp_acl_remove, | |
}, | |
STEXI | |
@item acl_remove @var{aclname} @var{match} | |
@findex acl_remove | |
Remove the specified match rule from the access control list. | |
ETEXI | |
{ | |
.name = "acl_reset", | |
.args_type = "aclname:s", | |
.params = "aclname", | |
.help = "reset the access control list", | |
.cmd = hmp_acl_reset, | |
}, | |
STEXI | |
@item acl_reset @var{aclname} | |
@findex acl_reset | |
Remove all matches from the access control list, and set the default | |
policy back to @code{deny}. | |
ETEXI | |
{ | |
.name = "nbd_server_start", | |
.args_type = "all:-a,writable:-w,uri:s", | |
.params = "nbd_server_start [-a] [-w] host:port", | |
.help = "serve block devices on the given host and port", | |
.cmd = hmp_nbd_server_start, | |
}, | |
STEXI | |
@item nbd_server_start @var{host}:@var{port} | |
@findex nbd_server_start | |
Start an NBD server on the given host and/or port. If the @option{-a} | |
option is included, all of the virtual machine's block devices that | |
have an inserted media on them are automatically exported; in this case, | |
the @option{-w} option makes the devices writable too. | |
ETEXI | |
{ | |
.name = "nbd_server_add", | |
.args_type = "writable:-w,device:B", | |
.params = "nbd_server_add [-w] device", | |
.help = "export a block device via NBD", | |
.cmd = hmp_nbd_server_add, | |
}, | |
STEXI | |
@item nbd_server_add @var{device} | |
@findex nbd_server_add | |
Export a block device through QEMU's NBD server, which must be started | |
beforehand with @command{nbd_server_start}. The @option{-w} option makes the | |
exported device writable too. | |
ETEXI | |
{ | |
.name = "nbd_server_stop", | |
.args_type = "", | |
.params = "nbd_server_stop", | |
.help = "stop serving block devices using the NBD protocol", | |
.cmd = hmp_nbd_server_stop, | |
}, | |
STEXI | |
@item nbd_server_stop | |
@findex nbd_server_stop | |
Stop the QEMU embedded NBD server. | |
ETEXI | |
#if defined(TARGET_I386) | |
{ | |
.name = "mce", | |
.args_type = "broadcast:-b,cpu_index:i,bank:i,status:l,mcg_status:l,addr:l,misc:l", | |
.params = "[-b] cpu bank status mcgstatus addr misc", | |
.help = "inject a MCE on the given CPU [and broadcast to other CPUs with -b option]", | |
.cmd = hmp_mce, | |
}, | |
#endif | |
STEXI | |
@item mce @var{cpu} @var{bank} @var{status} @var{mcgstatus} @var{addr} @var{misc} | |
@findex mce (x86) | |
Inject an MCE on the given CPU (x86 only). | |
ETEXI | |
{ | |
.name = "getfd", | |
.args_type = "fdname:s", | |
.params = "getfd name", | |
.help = "receive a file descriptor via SCM rights and assign it a name", | |
.cmd = hmp_getfd, | |
}, | |
STEXI | |
@item getfd @var{fdname} | |
@findex getfd | |
If a file descriptor is passed alongside this command using the SCM_RIGHTS | |
mechanism on unix sockets, it is stored using the name @var{fdname} for | |
later use by other monitor commands. | |
ETEXI | |
{ | |
.name = "closefd", | |
.args_type = "fdname:s", | |
.params = "closefd name", | |
.help = "close a file descriptor previously passed via SCM rights", | |
.cmd = hmp_closefd, | |
}, | |
STEXI | |
@item closefd @var{fdname} | |
@findex closefd | |
Close the file descriptor previously assigned to @var{fdname} using the | |
@code{getfd} command. This is only needed if the file descriptor was never | |
used by another monitor command. | |
ETEXI | |
{ | |
.name = "block_passwd", | |
.args_type = "device:B,password:s", | |
.params = "block_passwd device password", | |
.help = "set the password of encrypted block devices", | |
.cmd = hmp_block_passwd, | |
}, | |
STEXI | |
@item block_passwd @var{device} @var{password} | |
@findex block_passwd | |
Set the encrypted device @var{device} password to @var{password} | |
This command is now obsolete and will always return an error since 2.10 | |
ETEXI | |
{ | |
.name = "block_set_io_throttle", | |
.args_type = "device:B,bps:l,bps_rd:l,bps_wr:l,iops:l,iops_rd:l,iops_wr:l", | |
.params = "device bps bps_rd bps_wr iops iops_rd iops_wr", | |
.help = "change I/O throttle limits for a block drive", | |
.cmd = hmp_block_set_io_throttle, | |
}, | |
STEXI | |
@item block_set_io_throttle @var{device} @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr} | |
@findex block_set_io_throttle | |
Change I/O throttle limits for a block drive to @var{bps} @var{bps_rd} @var{bps_wr} @var{iops} @var{iops_rd} @var{iops_wr} | |
ETEXI | |
{ | |
.name = "set_password", | |
.args_type = "protocol:s,password:s,connected:s?", | |
.params = "protocol password action-if-connected", | |
.help = "set spice/vnc password", | |
.cmd = hmp_set_password, | |
}, | |
STEXI | |
@item set_password [ vnc | spice ] password [ action-if-connected ] | |
@findex set_password | |
Change spice/vnc password. Use zero to make the password stay valid | |
forever. @var{action-if-connected} specifies what should happen in | |
case a connection is established: @var{fail} makes the password change | |
fail. @var{disconnect} changes the password and disconnects the | |
client. @var{keep} changes the password and keeps the connection up. | |
@var{keep} is the default. | |
ETEXI | |
{ | |
.name = "expire_password", | |
.args_type = "protocol:s,time:s", | |
.params = "protocol time", | |
.help = "set spice/vnc password expire-time", | |
.cmd = hmp_expire_password, | |
}, | |
STEXI | |
@item expire_password [ vnc | spice ] expire-time | |
@findex expire_password | |
Specify when a password for spice/vnc becomes | |
invalid. @var{expire-time} accepts: | |
@table @var | |
@item now | |
Invalidate password instantly. | |
@item never | |
Password stays valid forever. | |
@item +nsec | |
Password stays valid for @var{nsec} seconds starting now. | |
@item nsec | |
Password is invalidated at the given time. @var{nsec} are the seconds | |
passed since 1970, i.e. unix epoch. | |
@end table | |
ETEXI | |
{ | |
.name = "chardev-add", | |
.args_type = "args:s", | |
.params = "args", | |
.help = "add chardev", | |
.cmd = hmp_chardev_add, | |
.command_completion = chardev_add_completion, | |
}, | |
STEXI | |
@item chardev-add args | |
@findex chardev-add | |
chardev-add accepts the same parameters as the -chardev command line switch. | |
ETEXI | |
{ | |
.name = "chardev-change", | |
.args_type = "id:s,args:s", | |
.params = "id args", | |
.help = "change chardev", | |
.cmd = hmp_chardev_change, | |
}, | |
STEXI | |
@item chardev-change args | |
@findex chardev-change | |
chardev-change accepts existing chardev @var{id} and then the same arguments | |
as the -chardev command line switch (except for "id"). | |
ETEXI | |
{ | |
.name = "chardev-remove", | |
.args_type = "id:s", | |
.params = "id", | |
.help = "remove chardev", | |
.cmd = hmp_chardev_remove, | |
.command_completion = chardev_remove_completion, | |
}, | |
STEXI | |
@item chardev-remove id | |
@findex chardev-remove | |
Removes the chardev @var{id}. | |
ETEXI | |
{ | |
.name = "chardev-send-break", | |
.args_type = "id:s", | |
.params = "id", | |
.help = "send a break on chardev", | |
.cmd = hmp_chardev_send_break, | |
.command_completion = chardev_remove_completion, | |
}, | |
STEXI | |
@item chardev-send-break id | |
@findex chardev-send-break | |
Send a break on the chardev @var{id}. | |
ETEXI | |
{ | |
.name = "qemu-io", | |
.args_type = "device:B,command:s", | |
.params = "[device] \"[command]\"", | |
.help = "run a qemu-io command on a block device", | |
.cmd = hmp_qemu_io, | |
}, | |
STEXI | |
@item qemu-io @var{device} @var{command} | |
@findex qemu-io | |
Executes a qemu-io command on the given block device. | |
ETEXI | |
{ | |
.name = "cpu-add", | |
.args_type = "id:i", | |
.params = "id", | |
.help = "add cpu", | |
.cmd = hmp_cpu_add, | |
}, | |
STEXI | |
@item cpu-add @var{id} | |
@findex cpu-add | |
Add CPU with id @var{id} | |
ETEXI | |
{ | |
.name = "qom-list", | |
.args_type = "path:s?", | |
.params = "path", | |
.help = "list QOM properties", | |
.cmd = hmp_qom_list, | |
}, | |
STEXI | |
@item qom-list [@var{path}] | |
Print QOM properties of object at location @var{path} | |
ETEXI | |
{ | |
.name = "qom-set", | |
.args_type = "path:s,property:s,value:s", | |
.params = "path property value", | |
.help = "set QOM property", | |
.cmd = hmp_qom_set, | |
}, | |
STEXI | |
@item qom-set @var{path} @var{property} @var{value} | |
Set QOM property @var{property} of object at location @var{path} to value @var{value} | |
ETEXI | |
{ | |
.name = "info", | |
.args_type = "item:s?", | |
.params = "[subcommand]", | |
.help = "show various information about the system state", | |
.cmd = hmp_info_help, | |
.sub_table = info_cmds, | |
}, | |
STEXI | |
@end table | |
ETEXI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- Mode: makefile -*- | |
BUILD_DIR?=$(CURDIR)/.. | |
include ../config-host.mak | |
include config-target.mak | |
include config-devices.mak | |
include $(SRC_PATH)/rules.mak | |
$(call set-vpath, $(SRC_PATH):$(BUILD_DIR)) | |
ifdef CONFIG_LINUX | |
QEMU_CFLAGS += -I../linux-headers | |
endif | |
QEMU_CFLAGS += -I.. -I$(SRC_PATH)/target/$(TARGET_BASE_ARCH) -DNEED_CPU_H | |
QEMU_CFLAGS+=-I$(SRC_PATH)/include | |
ifdef CONFIG_USER_ONLY | |
# user emulator name | |
QEMU_PROG=qemu-$(TARGET_NAME) | |
QEMU_PROG_BUILD = $(QEMU_PROG) | |
else | |
# system emulator name | |
QEMU_PROG=qemu-system-$(TARGET_NAME)$(EXESUF) | |
ifneq (,$(findstring -mwindows,$(SDL_LIBS))) | |
# Terminate program name with a 'w' because the linker builds a windows executable. | |
QEMU_PROGW=qemu-system-$(TARGET_NAME)w$(EXESUF) | |
$(QEMU_PROG): $(QEMU_PROGW) | |
$(call quiet-command,$(OBJCOPY) --subsystem console $(QEMU_PROGW) $(QEMU_PROG),"GEN","$(TARGET_DIR)$(QEMU_PROG)") | |
QEMU_PROG_BUILD = $(QEMU_PROGW) | |
else | |
QEMU_PROG_BUILD = $(QEMU_PROG) | |
endif | |
endif | |
PROGS=$(QEMU_PROG) $(QEMU_PROGW) | |
STPFILES= | |
config-target.h: config-target.h-timestamp | |
config-target.h-timestamp: config-target.mak | |
ifdef CONFIG_TRACE_SYSTEMTAP | |
stap: $(QEMU_PROG).stp-installed $(QEMU_PROG).stp $(QEMU_PROG)-simpletrace.stp | |
ifdef CONFIG_USER_ONLY | |
TARGET_TYPE=user | |
else | |
TARGET_TYPE=system | |
endif | |
tracetool-y = $(SRC_PATH)/scripts/tracetool.py | |
tracetool-y += $(shell find $(SRC_PATH)/scripts/tracetool -name "*.py") | |
$(QEMU_PROG).stp-installed: $(BUILD_DIR)/trace-events-all $(tracetool-y) | |
$(call quiet-command,$(TRACETOOL) \ | |
--group=all \ | |
--format=stap \ | |
--backends=$(TRACE_BACKENDS) \ | |
--binary=$(bindir)/$(QEMU_PROG) \ | |
--target-name=$(TARGET_NAME) \ | |
--target-type=$(TARGET_TYPE) \ | |
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp-installed") | |
$(QEMU_PROG).stp: $(BUILD_DIR)/trace-events-all $(tracetool-y) | |
$(call quiet-command,$(TRACETOOL) \ | |
--group=all \ | |
--format=stap \ | |
--backends=$(TRACE_BACKENDS) \ | |
--binary=$(realpath .)/$(QEMU_PROG) \ | |
--target-name=$(TARGET_NAME) \ | |
--target-type=$(TARGET_TYPE) \ | |
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp") | |
$(QEMU_PROG)-simpletrace.stp: $(BUILD_DIR)/trace-events-all $(tracetool-y) | |
$(call quiet-command,$(TRACETOOL) \ | |
--group=all \ | |
--format=simpletrace-stap \ | |
--backends=$(TRACE_BACKENDS) \ | |
--probe-prefix=qemu.$(TARGET_TYPE).$(TARGET_NAME) \ | |
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG)-simpletrace.stp") | |
else | |
stap: | |
endif | |
.PHONY: stap | |
all: $(PROGS) stap | |
# Dummy command so that make thinks it has done something | |
@true | |
######################################################### | |
# cpu emulator library | |
obj-y += exec.o | |
obj-y += accel/ | |
obj-$(CONFIG_TCG) += tcg/tcg.o tcg/tcg-op.o tcg/optimize.o | |
obj-$(CONFIG_TCG) += tcg/tcg-common.o | |
obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o | |
obj-$(CONFIG_TCG_INTERPRETER) += disas/tci.o | |
obj-y += fpu/softfloat.o | |
obj-y += target/$(TARGET_BASE_ARCH)/ | |
obj-y += disas.o | |
obj-$(call notempty,$(TARGET_XML_FILES)) += gdbstub-xml.o | |
######################################################### | |
# Linux user emulator target | |
ifdef CONFIG_LINUX_USER | |
QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) \ | |
-I$(SRC_PATH)/linux-user/host/$(ARCH) \ | |
-I$(SRC_PATH)/linux-user | |
obj-y += linux-user/ | |
obj-y += gdbstub.o thunk.o | |
endif #CONFIG_LINUX_USER | |
######################################################### | |
# BSD user emulator target | |
ifdef CONFIG_BSD_USER | |
QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ABI_DIR) \ | |
-I$(SRC_PATH)/bsd-user/$(HOST_VARIANT_DIR) | |
obj-y += bsd-user/ | |
obj-y += gdbstub.o | |
endif #CONFIG_BSD_USER | |
######################################################### | |
# System emulator target | |
ifdef CONFIG_SOFTMMU | |
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o memory-access.o | |
obj-y += qtest.o | |
obj-y += hw/ | |
obj-y += memory.o | |
obj-y += memory_mapping.o | |
obj-y += dump.o | |
obj-y += migration/ram.o | |
LIBS := $(libs_softmmu) $(LIBS) | |
# Hardware support | |
ifeq ($(TARGET_NAME), sparc64) | |
obj-y += hw/sparc64/ | |
else | |
obj-y += hw/$(TARGET_BASE_ARCH)/ | |
endif | |
GENERATED_FILES += hmp-commands.h hmp-commands-info.h | |
endif # CONFIG_SOFTMMU | |
# Workaround for http://gcc.gnu.org/PR55489, see configure. | |
%/translate.o: QEMU_CFLAGS += $(TRANSLATE_OPT_CFLAGS) | |
dummy := $(call unnest-vars,,obj-y) | |
all-obj-y := $(obj-y) | |
target-obj-y := | |
block-obj-y := | |
common-obj-y := | |
chardev-obj-y := | |
include $(SRC_PATH)/Makefile.objs | |
dummy := $(call unnest-vars,,target-obj-y) | |
target-obj-y-save := $(target-obj-y) | |
dummy := $(call unnest-vars,.., \ | |
block-obj-y \ | |
block-obj-m \ | |
chardev-obj-y \ | |
crypto-obj-y \ | |
crypto-aes-obj-y \ | |
qom-obj-y \ | |
io-obj-y \ | |
common-obj-y \ | |
common-obj-m) | |
target-obj-y := $(target-obj-y-save) | |
all-obj-y += $(common-obj-y) | |
all-obj-y += $(target-obj-y) | |
all-obj-y += $(qom-obj-y) | |
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y) | |
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y) | |
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y) | |
all-obj-$(CONFIG_SOFTMMU) += $(io-obj-y) | |
$(QEMU_PROG_BUILD): config-devices.mak | |
COMMON_LDADDS = ../libqemuutil.a | |
# build either PROG or PROGW | |
$(QEMU_PROG_BUILD): $(all-obj-y) $(COMMON_LDADDS) | |
$(call LINK, $(filter-out %.mak, $^)) | |
ifdef CONFIG_DARWIN | |
$(call quiet-command,Rez -append $(SRC_PATH)/pc-bios/qemu.rsrc -o $@,"REZ","$(TARGET_DIR)$@") | |
$(call quiet-command,SetFile -a C $@,"SETFILE","$(TARGET_DIR)$@") | |
endif | |
gdbstub-xml.c: $(TARGET_XML_FILES) $(SRC_PATH)/scripts/feature_to_c.sh | |
$(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES),"GEN","$(TARGET_DIR)$@") | |
hmp-commands.h: $(SRC_PATH)/hmp-commands.hx $(SRC_PATH)/scripts/hxtool | |
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@") | |
hmp-commands-info.h: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxtool | |
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@") | |
clean: clean-target | |
rm -f *.a *~ $(PROGS) | |
rm -f $(shell find . -name '*.[od]') | |
rm -f hmp-commands.h gdbstub-xml.c | |
ifdef CONFIG_TRACE_SYSTEMTAP | |
rm -f *.stp | |
endif | |
install: all | |
ifneq ($(PROGS),) | |
$(call install-prog,$(PROGS),$(DESTDIR)$(bindir)) | |
endif | |
ifdef CONFIG_TRACE_SYSTEMTAP | |
$(INSTALL_DIR) "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset" | |
$(INSTALL_DATA) $(QEMU_PROG).stp-installed "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG).stp" | |
$(INSTALL_DATA) $(QEMU_PROG)-simpletrace.stp "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG)-simpletrace.stp" | |
endif | |
GENERATED_FILES += config-target.h | |
Makefile: $(GENERATED_FILES) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Acess guest physical memory via a domain socket. | |
* | |
* Copyright (C) 2011 Sandia National Laboratories | |
* Author: Bryan D. Payne ([email protected]) | |
*/ | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <sys/un.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <stdint.h> | |
#include "memory-access.h" | |
//#include "cpu-all.h" | |
#include "qemu/osdep.h" | |
#include "qemu-common.h" | |
#include "exec/cpu-common.h" | |
//#include "config.h" | |
struct request{ | |
uint8_t type; // 0 quit, 1 read, 2 write, ... rest reserved | |
uint64_t address; // address to read from OR write to | |
uint64_t length; // number of bytes to read OR write | |
}; | |
static uint64_t | |
connection_read_memory (uint64_t user_paddr, void *buf, uint64_t user_len) | |
{ | |
hwaddr paddr = (hwaddr) user_paddr; | |
hwaddr len = (hwaddr) user_len; | |
void *guestmem = cpu_physical_memory_map(paddr, &len, 0); | |
if (!guestmem){ | |
return 0; | |
} | |
memcpy(buf, guestmem, len); | |
cpu_physical_memory_unmap(guestmem, len, 0, len); | |
return len; | |
} | |
static uint64_t | |
connection_write_memory (uint64_t user_paddr, void *buf, uint64_t user_len) | |
{ | |
hwaddr paddr = (hwaddr) user_paddr; | |
hwaddr len = (hwaddr) user_len; | |
void *guestmem = cpu_physical_memory_map(paddr, &len, 1); | |
if (!guestmem){ | |
return 0; | |
} | |
memcpy(guestmem, buf, len); | |
cpu_physical_memory_unmap(guestmem, len, 0, len); | |
return len; | |
} | |
static void | |
send_success_ack (int connection_fd) | |
{ | |
uint8_t success = 1; | |
int nbytes = write(connection_fd, &success, 1); | |
if (1 != nbytes){ | |
printf("QemuMemoryAccess: failed to send success ack\n"); | |
} | |
} | |
static void | |
send_fail_ack (int connection_fd) | |
{ | |
uint8_t fail = 0; | |
int nbytes = write(connection_fd, &fail, 1); | |
if (1 != nbytes){ | |
printf("QemuMemoryAccess: failed to send fail ack\n"); | |
} | |
} | |
static void | |
connection_handler (int connection_fd) | |
{ | |
int nbytes; | |
struct request req; | |
while (1){ | |
// client request should match the struct request format | |
nbytes = read(connection_fd, &req, sizeof(struct request)); | |
if (nbytes != sizeof(struct request)){ | |
// error | |
continue; | |
} | |
else if (req.type == 0){ | |
// request to quit, goodbye | |
break; | |
} | |
else if (req.type == 1){ | |
// request to read | |
char *buf = malloc(req.length + 1); | |
nbytes = connection_read_memory(req.address, buf, req.length); | |
if (nbytes != req.length){ | |
// read failure, return failure message | |
buf[req.length] = 0; // set last byte to 0 for failure | |
nbytes = write(connection_fd, buf, req.length + 1); | |
} | |
else{ | |
// read success, return bytes | |
buf[req.length] = 1; // set last byte to 1 for success | |
nbytes = write(connection_fd, buf, req.length + 1); | |
} | |
free(buf); | |
} | |
else if (req.type == 2){ | |
// request to write | |
void *write_buf = malloc(req.length); | |
nbytes = read(connection_fd, write_buf, req.length); | |
if (nbytes != req.length){ | |
// failed reading the message to write | |
send_fail_ack(connection_fd); | |
} | |
else{ | |
// do the write | |
nbytes = connection_write_memory(req.address, write_buf, req.length); | |
if (nbytes == req.length){ | |
send_success_ack(connection_fd); | |
} | |
else{ | |
send_fail_ack(connection_fd); | |
} | |
} | |
free(write_buf); | |
} | |
else{ | |
// unknown command | |
printf("QemuMemoryAccess: ignoring unknown command (%d)\n", req.type); | |
char *buf = malloc(1); | |
buf[0] = 0; | |
nbytes = write(connection_fd, buf, 1); | |
free(buf); | |
} | |
} | |
close(connection_fd); | |
} | |
static void * | |
connection_handler_gate (void *fd) | |
{ | |
connection_handler(*(int *)fd); | |
printf("QemuMemoryAccess: Connection done (%d)\n", *(int *)fd); | |
free(fd); | |
return NULL; | |
} | |
static void * | |
memory_access_thread (void *path) | |
{ | |
struct sockaddr_un address; | |
int socket_fd, connection_fd, *tmp_fd; | |
pthread_t thread; | |
socklen_t address_length; | |
socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); | |
if (socket_fd < 0){ | |
printf("QemuMemoryAccess: socket failed\n"); | |
goto error_exit; | |
} | |
unlink(path); | |
address.sun_family = AF_UNIX; | |
address_length = sizeof(address.sun_family) + sprintf(address.sun_path, "%s", (char *) path); | |
if (bind(socket_fd, (struct sockaddr *) &address, address_length) != 0){ | |
printf("QemuMemoryAccess: bind failed\n"); | |
goto error_exit; | |
} | |
if (listen(socket_fd, 0) != 0){ | |
printf("QemuMemoryAccess: listen failed\n"); | |
goto error_exit; | |
} | |
while (true) { | |
connection_fd = accept(socket_fd, (struct sockaddr *) &address, &address_length); | |
printf("QemuMemoryAccess: Connction accepted on %d.\n", connection_fd); | |
tmp_fd = (int *) calloc(1, sizeof(int)); | |
*tmp_fd = connection_fd; | |
pthread_create(&thread, NULL, connection_handler_gate, tmp_fd); | |
} | |
close(socket_fd); | |
unlink(path); | |
error_exit: | |
return NULL; | |
} | |
int | |
memory_access_start (const char *path) | |
{ | |
pthread_t thread; | |
sigset_t set, oldset; | |
int ret; | |
// create a copy of path that we can safely use | |
char *pathcopy = malloc(strlen(path) + 1); | |
memcpy(pathcopy, path, strlen(path) + 1); | |
// start the thread | |
sigfillset(&set); | |
pthread_sigmask(SIG_SETMASK, &set, &oldset); | |
ret = pthread_create(&thread, NULL, memory_access_thread, pathcopy); | |
pthread_sigmask(SIG_SETMASK, &oldset, NULL); | |
return ret; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* QEMU monitor | |
* | |
* Copyright (c) 2003-2004 Fabrice Bellard | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
* THE SOFTWARE. | |
*/ | |
#include "qemu/osdep.h" | |
#include <dirent.h> | |
#include "qemu-common.h" | |
#include "cpu.h" | |
#include "hw/hw.h" | |
#include "monitor/qdev.h" | |
#include "hw/usb.h" | |
#include "hw/i386/pc.h" | |
#include "hw/pci/pci.h" | |
#include "sysemu/watchdog.h" | |
#include "hw/loader.h" | |
#include "exec/gdbstub.h" | |
#include "net/net.h" | |
#include "net/slirp.h" | |
#include "chardev/char-fe.h" | |
#include "ui/qemu-spice.h" | |
#include "sysemu/numa.h" | |
#include "monitor/monitor.h" | |
#include "qemu/config-file.h" | |
#include "qemu/readline.h" | |
#include "ui/console.h" | |
#include "ui/input.h" | |
#include "sysemu/blockdev.h" | |
#include "sysemu/block-backend.h" | |
#include "audio/audio.h" | |
#include "disas/disas.h" | |
#include "sysemu/balloon.h" | |
#include "qemu/timer.h" | |
#include "sysemu/hw_accel.h" | |
#include "qemu/acl.h" | |
#include "sysemu/tpm.h" | |
#include "qapi/qmp/qerror.h" | |
#include "qapi/qmp/types.h" | |
#include "qapi/qmp/qjson.h" | |
#include "qapi/qmp/json-streamer.h" | |
#include "qapi/qmp/json-parser.h" | |
#include "qom/object_interfaces.h" | |
#include "trace-root.h" | |
#include "trace/control.h" | |
#include "monitor/hmp-target.h" | |
#ifdef CONFIG_TRACE_SIMPLE | |
#include "trace/simple.h" | |
#endif | |
#include "exec/memory.h" | |
#include "exec/exec-all.h" | |
#include "qemu/log.h" | |
#include "qmp-commands.h" | |
#include "hmp.h" | |
#include "qemu/thread.h" | |
#include "block/qapi.h" | |
#include "qapi/qmp-event.h" | |
#include "qapi-event.h" | |
#include "qmp-introspect.h" | |
#include "sysemu/qtest.h" | |
#include "sysemu/cpus.h" | |
#include "qemu/cutils.h" | |
#include "qapi/qmp/dispatch.h" | |
#if defined(TARGET_S390X) | |
#include "hw/s390x/storage-keys.h" | |
#include "hw/s390x/storage-attributes.h" | |
#endif | |
/* expose guest memory */ | |
#include "memory-access.h" | |
/* | |
* Supported types: | |
* | |
* 'F' filename | |
* 'B' block device name | |
* 's' string (accept optional quote) | |
* 'S' it just appends the rest of the string (accept optional quote) | |
* 'O' option string of the form NAME=VALUE,... | |
* parsed according to QemuOptsList given by its name | |
* Example: 'device:O' uses qemu_device_opts. | |
* Restriction: only lists with empty desc are supported | |
* TODO lift the restriction | |
* 'i' 32 bit integer | |
* 'l' target long (32 or 64 bit) | |
* 'M' Non-negative target long (32 or 64 bit), in user mode the | |
* value is multiplied by 2^20 (think Mebibyte) | |
* 'o' octets (aka bytes) | |
* user mode accepts an optional E, e, P, p, T, t, G, g, M, m, | |
* K, k suffix, which multiplies the value by 2^60 for suffixes E | |
* and e, 2^50 for suffixes P and p, 2^40 for suffixes T and t, | |
* 2^30 for suffixes G and g, 2^20 for M and m, 2^10 for K and k | |
* 'T' double | |
* user mode accepts an optional ms, us, ns suffix, | |
* which divides the value by 1e3, 1e6, 1e9, respectively | |
* '/' optional gdb-like print format (like "/10x") | |
* | |
* '?' optional type (for all types, except '/') | |
* '.' other form of optional type (for 'i' and 'l') | |
* 'b' boolean | |
* user mode accepts "on" or "off" | |
* '-' optional parameter (eg. '-f') | |
* | |
*/ | |
typedef struct mon_cmd_t { | |
const char *name; | |
const char *args_type; | |
const char *params; | |
const char *help; | |
void (*cmd)(Monitor *mon, const QDict *qdict); | |
/* @sub_table is a list of 2nd level of commands. If it does not exist, | |
* cmd should be used. If it exists, sub_table[?].cmd should be | |
* used, and cmd of 1st level plays the role of help function. | |
*/ | |
struct mon_cmd_t *sub_table; | |
void (*command_completion)(ReadLineState *rs, int nb_args, const char *str); | |
} mon_cmd_t; | |
/* file descriptors passed via SCM_RIGHTS */ | |
typedef struct mon_fd_t mon_fd_t; | |
struct mon_fd_t { | |
char *name; | |
int fd; | |
QLIST_ENTRY(mon_fd_t) next; | |
}; | |
/* file descriptor associated with a file descriptor set */ | |
typedef struct MonFdsetFd MonFdsetFd; | |
struct MonFdsetFd { | |
int fd; | |
bool removed; | |
char *opaque; | |
QLIST_ENTRY(MonFdsetFd) next; | |
}; | |
/* file descriptor set containing fds passed via SCM_RIGHTS */ | |
typedef struct MonFdset MonFdset; | |
struct MonFdset { | |
int64_t id; | |
QLIST_HEAD(, MonFdsetFd) fds; | |
QLIST_HEAD(, MonFdsetFd) dup_fds; | |
QLIST_ENTRY(MonFdset) next; | |
}; | |
typedef struct { | |
JSONMessageParser parser; | |
/* | |
* When a client connects, we're in capabilities negotiation mode. | |
* When command qmp_capabilities succeeds, we go into command | |
* mode. | |
*/ | |
QmpCommandList *commands; | |
} MonitorQMP; | |
/* | |
* To prevent flooding clients, events can be throttled. The | |
* throttling is calculated globally, rather than per-Monitor | |
* instance. | |
*/ | |
typedef struct MonitorQAPIEventState { | |
QAPIEvent event; /* Throttling state for this event type and... */ | |
QDict *data; /* ... data, see qapi_event_throttle_equal() */ | |
QEMUTimer *timer; /* Timer for handling delayed events */ | |
QDict *qdict; /* Delayed event (if any) */ | |
} MonitorQAPIEventState; | |
typedef struct { | |
int64_t rate; /* Minimum time (in ns) between two events */ | |
} MonitorQAPIEventConf; | |
struct Monitor { | |
CharBackend chr; | |
int reset_seen; | |
int flags; | |
int suspend_cnt; | |
bool skip_flush; | |
QemuMutex out_lock; | |
QString *outbuf; | |
guint out_watch; | |
/* Read under either BQL or out_lock, written with BQL+out_lock. */ | |
int mux_out; | |
ReadLineState *rs; | |
MonitorQMP qmp; | |
gchar *mon_cpu_path; | |
BlockCompletionFunc *password_completion_cb; | |
void *password_opaque; | |
mon_cmd_t *cmd_table; | |
QLIST_HEAD(,mon_fd_t) fds; | |
QLIST_ENTRY(Monitor) entry; | |
}; | |
/* QMP checker flags */ | |
#define QMP_ACCEPT_UNKNOWNS 1 | |
/* Protects mon_list, monitor_event_state. */ | |
static QemuMutex monitor_lock; | |
static QLIST_HEAD(mon_list, Monitor) mon_list; | |
static QLIST_HEAD(mon_fdsets, MonFdset) mon_fdsets; | |
static int mon_refcount; | |
static mon_cmd_t mon_cmds[]; | |
static mon_cmd_t info_cmds[]; | |
QmpCommandList qmp_commands, qmp_cap_negotiation_commands; | |
Monitor *cur_mon; | |
static QEMUClockType event_clock_type = QEMU_CLOCK_REALTIME; | |
static void monitor_command_cb(void *opaque, const char *cmdline, | |
void *readline_opaque); | |
/** | |
* Is @mon a QMP monitor? | |
*/ | |
static inline bool monitor_is_qmp(const Monitor *mon) | |
{ | |
return (mon->flags & MONITOR_USE_CONTROL); | |
} | |
/** | |
* Is the current monitor, if any, a QMP monitor? | |
*/ | |
bool monitor_cur_is_qmp(void) | |
{ | |
return cur_mon && monitor_is_qmp(cur_mon); | |
} | |
void monitor_read_command(Monitor *mon, int show_prompt) | |
{ | |
if (!mon->rs) | |
return; | |
readline_start(mon->rs, "(qemu) ", 0, monitor_command_cb, NULL); | |
if (show_prompt) | |
readline_show_prompt(mon->rs); | |
} | |
int monitor_read_password(Monitor *mon, ReadLineFunc *readline_func, | |
void *opaque) | |
{ | |
if (mon->rs) { | |
readline_start(mon->rs, "Password: ", 1, readline_func, opaque); | |
/* prompt is printed on return from the command handler */ | |
return 0; | |
} else { | |
monitor_printf(mon, "terminal does not support password prompting\n"); | |
return -ENOTTY; | |
} | |
} | |
static void monitor_flush_locked(Monitor *mon); | |
static gboolean monitor_unblocked(GIOChannel *chan, GIOCondition cond, | |
void *opaque) | |
{ | |
Monitor *mon = opaque; | |
qemu_mutex_lock(&mon->out_lock); | |
mon->out_watch = 0; | |
monitor_flush_locked(mon); | |
qemu_mutex_unlock(&mon->out_lock); | |
return FALSE; | |
} | |
/* Called with mon->out_lock held. */ | |
static void monitor_flush_locked(Monitor *mon) | |
{ | |
int rc; | |
size_t len; | |
const char *buf; | |
if (mon->skip_flush) { | |
return; | |
} | |
buf = qstring_get_str(mon->outbuf); | |
len = qstring_get_length(mon->outbuf); | |
if (len && !mon->mux_out) { | |
rc = qemu_chr_fe_write(&mon->chr, (const uint8_t *) buf, len); | |
if ((rc < 0 && errno != EAGAIN) || (rc == len)) { | |
/* all flushed or error */ | |
QDECREF(mon->outbuf); | |
mon->outbuf = qstring_new(); | |
return; | |
} | |
if (rc > 0) { | |
/* partial write */ | |
QString *tmp = qstring_from_str(buf + rc); | |
QDECREF(mon->outbuf); | |
mon->outbuf = tmp; | |
} | |
if (mon->out_watch == 0) { | |
mon->out_watch = | |
qemu_chr_fe_add_watch(&mon->chr, G_IO_OUT | G_IO_HUP, | |
monitor_unblocked, mon); | |
} | |
} | |
} | |
void monitor_flush(Monitor *mon) | |
{ | |
qemu_mutex_lock(&mon->out_lock); | |
monitor_flush_locked(mon); | |
qemu_mutex_unlock(&mon->out_lock); | |
} | |
/* flush at every end of line */ | |
static void monitor_puts(Monitor *mon, const char *str) | |
{ | |
char c; | |
qemu_mutex_lock(&mon->out_lock); | |
for(;;) { | |
c = *str++; | |
if (c == '\0') | |
break; | |
if (c == '\n') { | |
qstring_append_chr(mon->outbuf, '\r'); | |
} | |
qstring_append_chr(mon->outbuf, c); | |
if (c == '\n') { | |
monitor_flush_locked(mon); | |
} | |
} | |
qemu_mutex_unlock(&mon->out_lock); | |
} | |
void monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) | |
{ | |
char *buf; | |
if (!mon) | |
return; | |
if (monitor_is_qmp(mon)) { | |
return; | |
} | |
buf = g_strdup_vprintf(fmt, ap); | |
monitor_puts(mon, buf); | |
g_free(buf); | |
} | |
void monitor_printf(Monitor *mon, const char *fmt, ...) | |
{ | |
va_list ap; | |
va_start(ap, fmt); | |
monitor_vprintf(mon, fmt, ap); | |
va_end(ap); | |
} | |
int monitor_fprintf(FILE *stream, const char *fmt, ...) | |
{ | |
va_list ap; | |
va_start(ap, fmt); | |
monitor_vprintf((Monitor *)stream, fmt, ap); | |
va_end(ap); | |
return 0; | |
} | |
static void monitor_json_emitter(Monitor *mon, const QObject *data) | |
{ | |
QString *json; | |
json = mon->flags & MONITOR_USE_PRETTY ? qobject_to_json_pretty(data) : | |
qobject_to_json(data); | |
assert(json != NULL); | |
qstring_append_chr(json, '\n'); | |
monitor_puts(mon, qstring_get_str(json)); | |
QDECREF(json); | |
} | |
static MonitorQAPIEventConf monitor_qapi_event_conf[QAPI_EVENT__MAX] = { | |
/* Limit guest-triggerable events to 1 per second */ | |
[QAPI_EVENT_RTC_CHANGE] = { 1000 * SCALE_MS }, | |
[QAPI_EVENT_WATCHDOG] = { 1000 * SCALE_MS }, | |
[QAPI_EVENT_BALLOON_CHANGE] = { 1000 * SCALE_MS }, | |
[QAPI_EVENT_QUORUM_REPORT_BAD] = { 1000 * SCALE_MS }, | |
[QAPI_EVENT_QUORUM_FAILURE] = { 1000 * SCALE_MS }, | |
[QAPI_EVENT_VSERPORT_CHANGE] = { 1000 * SCALE_MS }, | |
}; | |
GHashTable *monitor_qapi_event_state; | |
/* | |
* Emits the event to every monitor instance, @event is only used for trace | |
* Called with monitor_lock held. | |
*/ | |
static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict) | |
{ | |
Monitor *mon; | |
trace_monitor_protocol_event_emit(event, qdict); | |
QLIST_FOREACH(mon, &mon_list, entry) { | |
if (monitor_is_qmp(mon) | |
&& mon->qmp.commands != &qmp_cap_negotiation_commands) { | |
monitor_json_emitter(mon, QOBJECT(qdict)); | |
} | |
} | |
} | |
static void monitor_qapi_event_handler(void *opaque); | |
/* | |
* Queue a new event for emission to Monitor instances, | |
* applying any rate limiting if required. | |
*/ | |
static void | |
monitor_qapi_event_queue(QAPIEvent event, QDict *qdict, Error **errp) | |
{ | |
MonitorQAPIEventConf *evconf; | |
MonitorQAPIEventState *evstate; | |
assert(event < QAPI_EVENT__MAX); | |
evconf = &monitor_qapi_event_conf[event]; | |
trace_monitor_protocol_event_queue(event, qdict, evconf->rate); | |
qemu_mutex_lock(&monitor_lock); | |
if (!evconf->rate) { | |
/* Unthrottled event */ | |
monitor_qapi_event_emit(event, qdict); | |
} else { | |
QDict *data = qobject_to_qdict(qdict_get(qdict, "data")); | |
MonitorQAPIEventState key = { .event = event, .data = data }; | |
evstate = g_hash_table_lookup(monitor_qapi_event_state, &key); | |
assert(!evstate || timer_pending(evstate->timer)); | |
if (evstate) { | |
/* | |
* Timer is pending for (at least) evconf->rate ns after | |
* last send. Store event for sending when timer fires, | |
* replacing a prior stored event if any. | |
*/ | |
QDECREF(evstate->qdict); | |
evstate->qdict = qdict; | |
QINCREF(evstate->qdict); | |
} else { | |
/* | |
* Last send was (at least) evconf->rate ns ago. | |
* Send immediately, and arm the timer to call | |
* monitor_qapi_event_handler() in evconf->rate ns. Any | |
* events arriving before then will be delayed until then. | |
*/ | |
int64_t now = qemu_clock_get_ns(event_clock_type); | |
monitor_qapi_event_emit(event, qdict); | |
evstate = g_new(MonitorQAPIEventState, 1); | |
evstate->event = event; | |
evstate->data = data; | |
QINCREF(evstate->data); | |
evstate->qdict = NULL; | |
evstate->timer = timer_new_ns(event_clock_type, | |
monitor_qapi_event_handler, | |
evstate); | |
g_hash_table_add(monitor_qapi_event_state, evstate); | |
timer_mod_ns(evstate->timer, now + evconf->rate); | |
} | |
} | |
qemu_mutex_unlock(&monitor_lock); | |
} | |
/* | |
* This function runs evconf->rate ns after sending a throttled | |
* event. | |
* If another event has since been stored, send it. | |
*/ | |
static void monitor_qapi_event_handler(void *opaque) | |
{ | |
MonitorQAPIEventState *evstate = opaque; | |
MonitorQAPIEventConf *evconf = &monitor_qapi_event_conf[evstate->event]; | |
trace_monitor_protocol_event_handler(evstate->event, evstate->qdict); | |
qemu_mutex_lock(&monitor_lock); | |
if (evstate->qdict) { | |
int64_t now = qemu_clock_get_ns(event_clock_type); | |
monitor_qapi_event_emit(evstate->event, evstate->qdict); | |
QDECREF(evstate->qdict); | |
evstate->qdict = NULL; | |
timer_mod_ns(evstate->timer, now + evconf->rate); | |
} else { | |
g_hash_table_remove(monitor_qapi_event_state, evstate); | |
QDECREF(evstate->data); | |
timer_free(evstate->timer); | |
g_free(evstate); | |
} | |
qemu_mutex_unlock(&monitor_lock); | |
} | |
static unsigned int qapi_event_throttle_hash(const void *key) | |
{ | |
const MonitorQAPIEventState *evstate = key; | |
unsigned int hash = evstate->event * 255; | |
if (evstate->event == QAPI_EVENT_VSERPORT_CHANGE) { | |
hash += g_str_hash(qdict_get_str(evstate->data, "id")); | |
} | |
if (evstate->event == QAPI_EVENT_QUORUM_REPORT_BAD) { | |
hash += g_str_hash(qdict_get_str(evstate->data, "node-name")); | |
} | |
return hash; | |
} | |
static gboolean qapi_event_throttle_equal(const void *a, const void *b) | |
{ | |
const MonitorQAPIEventState *eva = a; | |
const MonitorQAPIEventState *evb = b; | |
if (eva->event != evb->event) { | |
return FALSE; | |
} | |
if (eva->event == QAPI_EVENT_VSERPORT_CHANGE) { | |
return !strcmp(qdict_get_str(eva->data, "id"), | |
qdict_get_str(evb->data, "id")); | |
} | |
if (eva->event == QAPI_EVENT_QUORUM_REPORT_BAD) { | |
return !strcmp(qdict_get_str(eva->data, "node-name"), | |
qdict_get_str(evb->data, "node-name")); | |
} | |
return TRUE; | |
} | |
static void monitor_qapi_event_init(void) | |
{ | |
if (qtest_enabled()) { | |
event_clock_type = QEMU_CLOCK_VIRTUAL; | |
} | |
monitor_qapi_event_state = g_hash_table_new(qapi_event_throttle_hash, | |
qapi_event_throttle_equal); | |
qmp_event_set_func_emit(monitor_qapi_event_queue); | |
} | |
static void handle_hmp_command(Monitor *mon, const char *cmdline); | |
static void monitor_data_init(Monitor *mon) | |
{ | |
memset(mon, 0, sizeof(Monitor)); | |
qemu_mutex_init(&mon->out_lock); | |
mon->outbuf = qstring_new(); | |
/* Use *mon_cmds by default. */ | |
mon->cmd_table = mon_cmds; | |
} | |
static void monitor_data_destroy(Monitor *mon) | |
{ | |
g_free(mon->mon_cpu_path); | |
qemu_chr_fe_deinit(&mon->chr, false); | |
if (monitor_is_qmp(mon)) { | |
json_message_parser_destroy(&mon->qmp.parser); | |
} | |
g_free(mon->rs); | |
QDECREF(mon->outbuf); | |
qemu_mutex_destroy(&mon->out_lock); | |
} | |
char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index, | |
int64_t cpu_index, Error **errp) | |
{ | |
char *output = NULL; | |
Monitor *old_mon, hmp; | |
monitor_data_init(&hmp); | |
hmp.skip_flush = true; | |
old_mon = cur_mon; | |
cur_mon = &hmp; | |
if (has_cpu_index) { | |
int ret = monitor_set_cpu(cpu_index); | |
if (ret < 0) { | |
cur_mon = old_mon; | |
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index", | |
"a CPU number"); | |
goto out; | |
} | |
} | |
handle_hmp_command(&hmp, command_line); | |
cur_mon = old_mon; | |
qemu_mutex_lock(&hmp.out_lock); | |
if (qstring_get_length(hmp.outbuf) > 0) { | |
output = g_strdup(qstring_get_str(hmp.outbuf)); | |
} else { | |
output = g_strdup(""); | |
} | |
qemu_mutex_unlock(&hmp.out_lock); | |
out: | |
monitor_data_destroy(&hmp); | |
return output; | |
} | |
static int compare_cmd(const char *name, const char *list) | |
{ | |
const char *p, *pstart; | |
int len; | |
len = strlen(name); | |
p = list; | |
for(;;) { | |
pstart = p; | |
p = strchr(p, '|'); | |
if (!p) | |
p = pstart + strlen(pstart); | |
if ((p - pstart) == len && !memcmp(pstart, name, len)) | |
return 1; | |
if (*p == '\0') | |
break; | |
p++; | |
} | |
return 0; | |
} | |
static int get_str(char *buf, int buf_size, const char **pp) | |
{ | |
const char *p; | |
char *q; | |
int c; | |
q = buf; | |
p = *pp; | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
if (*p == '\0') { | |
fail: | |
*q = '\0'; | |
*pp = p; | |
return -1; | |
} | |
if (*p == '\"') { | |
p++; | |
while (*p != '\0' && *p != '\"') { | |
if (*p == '\\') { | |
p++; | |
c = *p++; | |
switch (c) { | |
case 'n': | |
c = '\n'; | |
break; | |
case 'r': | |
c = '\r'; | |
break; | |
case '\\': | |
case '\'': | |
case '\"': | |
break; | |
default: | |
printf("unsupported escape code: '\\%c'\n", c); | |
goto fail; | |
} | |
if ((q - buf) < buf_size - 1) { | |
*q++ = c; | |
} | |
} else { | |
if ((q - buf) < buf_size - 1) { | |
*q++ = *p; | |
} | |
p++; | |
} | |
} | |
if (*p != '\"') { | |
printf("unterminated string\n"); | |
goto fail; | |
} | |
p++; | |
} else { | |
while (*p != '\0' && !qemu_isspace(*p)) { | |
if ((q - buf) < buf_size - 1) { | |
*q++ = *p; | |
} | |
p++; | |
} | |
} | |
*q = '\0'; | |
*pp = p; | |
return 0; | |
} | |
#define MAX_ARGS 16 | |
static void free_cmdline_args(char **args, int nb_args) | |
{ | |
int i; | |
assert(nb_args <= MAX_ARGS); | |
for (i = 0; i < nb_args; i++) { | |
g_free(args[i]); | |
} | |
} | |
/* | |
* Parse the command line to get valid args. | |
* @cmdline: command line to be parsed. | |
* @pnb_args: location to store the number of args, must NOT be NULL. | |
* @args: location to store the args, which should be freed by caller, must | |
* NOT be NULL. | |
* | |
* Returns 0 on success, negative on failure. | |
* | |
* NOTE: this parser is an approximate form of the real command parser. Number | |
* of args have a limit of MAX_ARGS. If cmdline contains more, it will | |
* return with failure. | |
*/ | |
static int parse_cmdline(const char *cmdline, | |
int *pnb_args, char **args) | |
{ | |
const char *p; | |
int nb_args, ret; | |
char buf[1024]; | |
p = cmdline; | |
nb_args = 0; | |
for (;;) { | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
if (*p == '\0') { | |
break; | |
} | |
if (nb_args >= MAX_ARGS) { | |
goto fail; | |
} | |
ret = get_str(buf, sizeof(buf), &p); | |
if (ret < 0) { | |
goto fail; | |
} | |
args[nb_args] = g_strdup(buf); | |
nb_args++; | |
} | |
*pnb_args = nb_args; | |
return 0; | |
fail: | |
free_cmdline_args(args, nb_args); | |
return -1; | |
} | |
static void help_cmd_dump_one(Monitor *mon, | |
const mon_cmd_t *cmd, | |
char **prefix_args, | |
int prefix_args_nb) | |
{ | |
int i; | |
for (i = 0; i < prefix_args_nb; i++) { | |
monitor_printf(mon, "%s ", prefix_args[i]); | |
} | |
monitor_printf(mon, "%s %s -- %s\n", cmd->name, cmd->params, cmd->help); | |
} | |
/* @args[@arg_index] is the valid command need to find in @cmds */ | |
static void help_cmd_dump(Monitor *mon, const mon_cmd_t *cmds, | |
char **args, int nb_args, int arg_index) | |
{ | |
const mon_cmd_t *cmd; | |
/* No valid arg need to compare with, dump all in *cmds */ | |
if (arg_index >= nb_args) { | |
for (cmd = cmds; cmd->name != NULL; cmd++) { | |
help_cmd_dump_one(mon, cmd, args, arg_index); | |
} | |
return; | |
} | |
/* Find one entry to dump */ | |
for (cmd = cmds; cmd->name != NULL; cmd++) { | |
if (compare_cmd(args[arg_index], cmd->name)) { | |
if (cmd->sub_table) { | |
/* continue with next arg */ | |
help_cmd_dump(mon, cmd->sub_table, | |
args, nb_args, arg_index + 1); | |
} else { | |
help_cmd_dump_one(mon, cmd, args, arg_index); | |
} | |
break; | |
} | |
} | |
} | |
static void help_cmd(Monitor *mon, const char *name) | |
{ | |
char *args[MAX_ARGS]; | |
int nb_args = 0; | |
/* 1. parse user input */ | |
if (name) { | |
/* special case for log, directly dump and return */ | |
if (!strcmp(name, "log")) { | |
const QEMULogItem *item; | |
monitor_printf(mon, "Log items (comma separated):\n"); | |
monitor_printf(mon, "%-10s %s\n", "none", "remove all logs"); | |
for (item = qemu_log_items; item->mask != 0; item++) { | |
monitor_printf(mon, "%-10s %s\n", item->name, item->help); | |
} | |
return; | |
} | |
if (parse_cmdline(name, &nb_args, args) < 0) { | |
return; | |
} | |
} | |
/* 2. dump the contents according to parsed args */ | |
help_cmd_dump(mon, mon->cmd_table, args, nb_args, 0); | |
free_cmdline_args(args, nb_args); | |
} | |
static void do_help_cmd(Monitor *mon, const QDict *qdict) | |
{ | |
help_cmd(mon, qdict_get_try_str(qdict, "name")); | |
} | |
static void hmp_trace_event(Monitor *mon, const QDict *qdict) | |
{ | |
const char *tp_name = qdict_get_str(qdict, "name"); | |
bool new_state = qdict_get_bool(qdict, "option"); | |
bool has_vcpu = qdict_haskey(qdict, "vcpu"); | |
int vcpu = qdict_get_try_int(qdict, "vcpu", 0); | |
Error *local_err = NULL; | |
if (vcpu < 0) { | |
monitor_printf(mon, "argument vcpu must be positive"); | |
return; | |
} | |
qmp_trace_event_set_state(tp_name, new_state, true, true, has_vcpu, vcpu, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
} | |
} | |
#ifdef CONFIG_TRACE_SIMPLE | |
static void hmp_trace_file(Monitor *mon, const QDict *qdict) | |
{ | |
const char *op = qdict_get_try_str(qdict, "op"); | |
const char *arg = qdict_get_try_str(qdict, "arg"); | |
if (!op) { | |
st_print_trace_file_status((FILE *)mon, &monitor_fprintf); | |
} else if (!strcmp(op, "on")) { | |
st_set_trace_file_enabled(true); | |
} else if (!strcmp(op, "off")) { | |
st_set_trace_file_enabled(false); | |
} else if (!strcmp(op, "flush")) { | |
st_flush_trace_buffer(); | |
} else if (!strcmp(op, "set")) { | |
if (arg) { | |
st_set_trace_file(arg); | |
} | |
} else { | |
monitor_printf(mon, "unexpected argument \"%s\"\n", op); | |
help_cmd(mon, "trace-file"); | |
} | |
} | |
#endif | |
static void hmp_info_help(Monitor *mon, const QDict *qdict) | |
{ | |
help_cmd(mon, "info"); | |
} | |
static void query_commands_cb(QmpCommand *cmd, void *opaque) | |
{ | |
CommandInfoList *info, **list = opaque; | |
if (!cmd->enabled) { | |
return; | |
} | |
info = g_malloc0(sizeof(*info)); | |
info->value = g_malloc0(sizeof(*info->value)); | |
info->value->name = g_strdup(cmd->name); | |
info->next = *list; | |
*list = info; | |
} | |
CommandInfoList *qmp_query_commands(Error **errp) | |
{ | |
CommandInfoList *list = NULL; | |
qmp_for_each_command(cur_mon->qmp.commands, query_commands_cb, &list); | |
return list; | |
} | |
EventInfoList *qmp_query_events(Error **errp) | |
{ | |
EventInfoList *info, *ev_list = NULL; | |
QAPIEvent e; | |
for (e = 0 ; e < QAPI_EVENT__MAX ; e++) { | |
const char *event_name = QAPIEvent_str(e); | |
assert(event_name != NULL); | |
info = g_malloc0(sizeof(*info)); | |
info->value = g_malloc0(sizeof(*info->value)); | |
info->value->name = g_strdup(event_name); | |
info->next = ev_list; | |
ev_list = info; | |
} | |
return ev_list; | |
} | |
/* | |
* Minor hack: generated marshalling suppressed for this command | |
* ('gen': false in the schema) so we can parse the JSON string | |
* directly into QObject instead of first parsing it with | |
* visit_type_SchemaInfoList() into a SchemaInfoList, then marshal it | |
* to QObject with generated output marshallers, every time. Instead, | |
* we do it in test-qobject-input-visitor.c, just to make sure | |
* qapi-introspect.py's output actually conforms to the schema. | |
*/ | |
static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data, | |
Error **errp) | |
{ | |
*ret_data = qobject_from_json(qmp_schema_json, &error_abort); | |
} | |
/* | |
* We used to define commands in qmp-commands.hx in addition to the | |
* QAPI schema. This permitted defining some of them only in certain | |
* configurations. query-commands has always reflected that (good, | |
* because it lets QMP clients figure out what's actually available), | |
* while query-qmp-schema never did (not so good). This function is a | |
* hack to keep the configuration-specific commands defined exactly as | |
* before, even though qmp-commands.hx is gone. | |
* | |
* FIXME Educate the QAPI schema on configuration-specific commands, | |
* and drop this hack. | |
*/ | |
static void qmp_unregister_commands_hack(void) | |
{ | |
#ifndef CONFIG_SPICE | |
qmp_unregister_command(&qmp_commands, "query-spice"); | |
#endif | |
#ifndef CONFIG_REPLICATION | |
qmp_unregister_command(&qmp_commands, "xen-set-replication"); | |
qmp_unregister_command(&qmp_commands, "query-xen-replication-status"); | |
qmp_unregister_command(&qmp_commands, "xen-colo-do-checkpoint"); | |
#endif | |
#ifndef TARGET_I386 | |
qmp_unregister_command(&qmp_commands, "rtc-reset-reinjection"); | |
#endif | |
#ifndef TARGET_S390X | |
qmp_unregister_command(&qmp_commands, "dump-skeys"); | |
#endif | |
#ifndef TARGET_ARM | |
qmp_unregister_command(&qmp_commands, "query-gic-capabilities"); | |
#endif | |
#if !defined(TARGET_S390X) && !defined(TARGET_I386) | |
qmp_unregister_command(&qmp_commands, "query-cpu-model-expansion"); | |
#endif | |
#if !defined(TARGET_S390X) | |
qmp_unregister_command(&qmp_commands, "query-cpu-model-baseline"); | |
qmp_unregister_command(&qmp_commands, "query-cpu-model-comparison"); | |
#endif | |
#if !defined(TARGET_PPC) && !defined(TARGET_ARM) && !defined(TARGET_I386) \ | |
&& !defined(TARGET_S390X) | |
qmp_unregister_command(&qmp_commands, "query-cpu-definitions"); | |
#endif | |
} | |
void monitor_init_qmp_commands(void) | |
{ | |
/* | |
* Two command lists: | |
* - qmp_commands contains all QMP commands | |
* - qmp_cap_negotiation_commands contains just | |
* "qmp_capabilities", to enforce capability negotiation | |
*/ | |
qmp_init_marshal(&qmp_commands); | |
qmp_register_command(&qmp_commands, "query-qmp-schema", | |
qmp_query_qmp_schema, | |
QCO_NO_OPTIONS); | |
qmp_register_command(&qmp_commands, "device_add", qmp_device_add, | |
QCO_NO_OPTIONS); | |
qmp_register_command(&qmp_commands, "netdev_add", qmp_netdev_add, | |
QCO_NO_OPTIONS); | |
qmp_unregister_commands_hack(); | |
QTAILQ_INIT(&qmp_cap_negotiation_commands); | |
qmp_register_command(&qmp_cap_negotiation_commands, "qmp_capabilities", | |
qmp_marshal_qmp_capabilities, QCO_NO_OPTIONS); | |
} | |
void qmp_qmp_capabilities(Error **errp) | |
{ | |
if (cur_mon->qmp.commands == &qmp_commands) { | |
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, | |
"Capabilities negotiation is already complete, command " | |
"ignored"); | |
return; | |
} | |
cur_mon->qmp.commands = &qmp_commands; | |
} | |
/* set the current CPU defined by the user */ | |
int monitor_set_cpu(int cpu_index) | |
{ | |
CPUState *cpu; | |
cpu = qemu_get_cpu(cpu_index); | |
if (cpu == NULL) { | |
return -1; | |
} | |
g_free(cur_mon->mon_cpu_path); | |
cur_mon->mon_cpu_path = object_get_canonical_path(OBJECT(cpu)); | |
return 0; | |
} | |
CPUState *mon_get_cpu(void) | |
{ | |
CPUState *cpu; | |
if (cur_mon->mon_cpu_path) { | |
cpu = (CPUState *) object_resolve_path_type(cur_mon->mon_cpu_path, | |
TYPE_CPU, NULL); | |
if (!cpu) { | |
g_free(cur_mon->mon_cpu_path); | |
cur_mon->mon_cpu_path = NULL; | |
} | |
} | |
if (!cur_mon->mon_cpu_path) { | |
if (!first_cpu) { | |
return NULL; | |
} | |
monitor_set_cpu(first_cpu->cpu_index); | |
cpu = first_cpu; | |
} | |
cpu_synchronize_state(cpu); | |
return cpu; | |
} | |
CPUArchState *mon_get_cpu_env(void) | |
{ | |
CPUState *cs = mon_get_cpu(); | |
return cs ? cs->env_ptr : NULL; | |
} | |
int monitor_get_cpu_index(void) | |
{ | |
CPUState *cs = mon_get_cpu(); | |
return cs ? cs->cpu_index : UNASSIGNED_CPU_INDEX; | |
} | |
static void hmp_info_registers(Monitor *mon, const QDict *qdict) | |
{ | |
bool all_cpus = qdict_get_try_bool(qdict, "cpustate_all", false); | |
CPUState *cs; | |
if (all_cpus) { | |
CPU_FOREACH(cs) { | |
monitor_printf(mon, "\nCPU#%d\n", cs->cpu_index); | |
cpu_dump_state(cs, (FILE *)mon, monitor_fprintf, CPU_DUMP_FPU); | |
} | |
} else { | |
cs = mon_get_cpu(); | |
if (!cs) { | |
monitor_printf(mon, "No CPU available\n"); | |
return; | |
} | |
cpu_dump_state(cs, (FILE *)mon, monitor_fprintf, CPU_DUMP_FPU); | |
} | |
} | |
#ifdef CONFIG_TCG | |
static void hmp_info_jit(Monitor *mon, const QDict *qdict) | |
{ | |
if (!tcg_enabled()) { | |
error_report("JIT information is only available with accel=tcg"); | |
return; | |
} | |
dump_exec_info((FILE *)mon, monitor_fprintf); | |
dump_drift_info((FILE *)mon, monitor_fprintf); | |
} | |
static void hmp_info_opcount(Monitor *mon, const QDict *qdict) | |
{ | |
dump_opcount_info((FILE *)mon, monitor_fprintf); | |
} | |
#endif | |
static void hmp_info_history(Monitor *mon, const QDict *qdict) | |
{ | |
int i; | |
const char *str; | |
if (!mon->rs) | |
return; | |
i = 0; | |
for(;;) { | |
str = readline_get_history(mon->rs, i); | |
if (!str) | |
break; | |
monitor_printf(mon, "%d: '%s'\n", i, str); | |
i++; | |
} | |
} | |
static void hmp_info_cpustats(Monitor *mon, const QDict *qdict) | |
{ | |
CPUState *cs = mon_get_cpu(); | |
if (!cs) { | |
monitor_printf(mon, "No CPU available\n"); | |
return; | |
} | |
cpu_dump_statistics(cs, (FILE *)mon, &monitor_fprintf, 0); | |
} | |
static void hmp_info_trace_events(Monitor *mon, const QDict *qdict) | |
{ | |
const char *name = qdict_get_try_str(qdict, "name"); | |
bool has_vcpu = qdict_haskey(qdict, "vcpu"); | |
int vcpu = qdict_get_try_int(qdict, "vcpu", 0); | |
TraceEventInfoList *events; | |
TraceEventInfoList *elem; | |
Error *local_err = NULL; | |
if (name == NULL) { | |
name = "*"; | |
} | |
if (vcpu < 0) { | |
monitor_printf(mon, "argument vcpu must be positive"); | |
return; | |
} | |
events = qmp_trace_event_get_state(name, has_vcpu, vcpu, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
return; | |
} | |
for (elem = events; elem != NULL; elem = elem->next) { | |
monitor_printf(mon, "%s : state %u\n", | |
elem->value->name, | |
elem->value->state == TRACE_EVENT_STATE_ENABLED ? 1 : 0); | |
} | |
qapi_free_TraceEventInfoList(events); | |
} | |
void qmp_client_migrate_info(const char *protocol, const char *hostname, | |
bool has_port, int64_t port, | |
bool has_tls_port, int64_t tls_port, | |
bool has_cert_subject, const char *cert_subject, | |
Error **errp) | |
{ | |
if (strcmp(protocol, "spice") == 0) { | |
if (!qemu_using_spice(errp)) { | |
return; | |
} | |
if (!has_port && !has_tls_port) { | |
error_setg(errp, QERR_MISSING_PARAMETER, "port/tls-port"); | |
return; | |
} | |
if (qemu_spice_migrate_info(hostname, | |
has_port ? port : -1, | |
has_tls_port ? tls_port : -1, | |
cert_subject)) { | |
error_setg(errp, QERR_UNDEFINED_ERROR); | |
return; | |
} | |
return; | |
} | |
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol", "spice"); | |
} | |
static void hmp_logfile(Monitor *mon, const QDict *qdict) | |
{ | |
Error *err = NULL; | |
qemu_set_log_filename(qdict_get_str(qdict, "filename"), &err); | |
if (err) { | |
error_report_err(err); | |
} | |
} | |
static void hmp_log(Monitor *mon, const QDict *qdict) | |
{ | |
int mask; | |
const char *items = qdict_get_str(qdict, "items"); | |
if (!strcmp(items, "none")) { | |
mask = 0; | |
} else { | |
mask = qemu_str_to_log_mask(items); | |
if (!mask) { | |
help_cmd(mon, "log"); | |
return; | |
} | |
} | |
qemu_set_log(mask); | |
} | |
static void hmp_singlestep(Monitor *mon, const QDict *qdict) | |
{ | |
const char *option = qdict_get_try_str(qdict, "option"); | |
if (!option || !strcmp(option, "on")) { | |
singlestep = 1; | |
} else if (!strcmp(option, "off")) { | |
singlestep = 0; | |
} else { | |
monitor_printf(mon, "unexpected option %s\n", option); | |
} | |
} | |
static void hmp_gdbserver(Monitor *mon, const QDict *qdict) | |
{ | |
const char *device = qdict_get_try_str(qdict, "device"); | |
if (!device) | |
device = "tcp::" DEFAULT_GDBSTUB_PORT; | |
if (gdbserver_start(device) < 0) { | |
monitor_printf(mon, "Could not open gdbserver on device '%s'\n", | |
device); | |
} else if (strcmp(device, "none") == 0) { | |
monitor_printf(mon, "Disabled gdbserver\n"); | |
} else { | |
monitor_printf(mon, "Waiting for gdb connection on device '%s'\n", | |
device); | |
} | |
} | |
static void hmp_watchdog_action(Monitor *mon, const QDict *qdict) | |
{ | |
const char *action = qdict_get_str(qdict, "action"); | |
if (select_watchdog_action(action) == -1) { | |
monitor_printf(mon, "Unknown watchdog action '%s'\n", action); | |
} | |
} | |
static void monitor_printc(Monitor *mon, int c) | |
{ | |
monitor_printf(mon, "'"); | |
switch(c) { | |
case '\'': | |
monitor_printf(mon, "\\'"); | |
break; | |
case '\\': | |
monitor_printf(mon, "\\\\"); | |
break; | |
case '\n': | |
monitor_printf(mon, "\\n"); | |
break; | |
case '\r': | |
monitor_printf(mon, "\\r"); | |
break; | |
default: | |
if (c >= 32 && c <= 126) { | |
monitor_printf(mon, "%c", c); | |
} else { | |
monitor_printf(mon, "\\x%02x", c); | |
} | |
break; | |
} | |
monitor_printf(mon, "'"); | |
} | |
static void memory_dump(Monitor *mon, int count, int format, int wsize, hwaddr addr, int is_physical) | |
{ | |
int l, line_size, i, max_digits, len; | |
uint8_t buf[16]; | |
uint64_t v; | |
CPUState *cs = mon_get_cpu(); | |
if (!cs && (format == 'i' || !is_physical)) { | |
monitor_printf(mon, "Can not dump without CPU\n"); | |
return; | |
} | |
if (format == 'i') { | |
monitor_disas(mon, cs, addr, count, is_physical); | |
return; | |
} | |
len = wsize * count; | |
if (wsize == 1) | |
line_size = 8; | |
else | |
line_size = 16; | |
max_digits = 0; | |
switch(format) { | |
case 'o': | |
max_digits = DIV_ROUND_UP(wsize * 8, 3); | |
break; | |
default: | |
case 'x': | |
max_digits = (wsize * 8) / 4; | |
break; | |
case 'u': | |
case 'd': | |
max_digits = DIV_ROUND_UP(wsize * 8 * 10, 33); | |
break; | |
case 'c': | |
wsize = 1; | |
break; | |
} | |
while (len > 0) { | |
if (is_physical) | |
monitor_printf(mon, TARGET_FMT_plx ":", addr); | |
else | |
monitor_printf(mon, TARGET_FMT_lx ":", (target_ulong)addr); | |
l = len; | |
if (l > line_size) | |
l = line_size; | |
if (is_physical) { | |
cpu_physical_memory_read(addr, buf, l); | |
} else { | |
if (cpu_memory_rw_debug(cs, addr, buf, l, 0) < 0) { | |
monitor_printf(mon, " Cannot access memory\n"); | |
break; | |
} | |
} | |
i = 0; | |
while (i < l) { | |
switch(wsize) { | |
default: | |
case 1: | |
v = ldub_p(buf + i); | |
break; | |
case 2: | |
v = lduw_p(buf + i); | |
break; | |
case 4: | |
v = (uint32_t)ldl_p(buf + i); | |
break; | |
case 8: | |
v = ldq_p(buf + i); | |
break; | |
} | |
monitor_printf(mon, " "); | |
switch(format) { | |
case 'o': | |
monitor_printf(mon, "%#*" PRIo64, max_digits, v); | |
break; | |
case 'x': | |
monitor_printf(mon, "0x%0*" PRIx64, max_digits, v); | |
break; | |
case 'u': | |
monitor_printf(mon, "%*" PRIu64, max_digits, v); | |
break; | |
case 'd': | |
monitor_printf(mon, "%*" PRId64, max_digits, v); | |
break; | |
case 'c': | |
monitor_printc(mon, v); | |
break; | |
} | |
i += wsize; | |
} | |
monitor_printf(mon, "\n"); | |
addr += l; | |
len -= l; | |
} | |
} | |
static void hmp_memory_dump(Monitor *mon, const QDict *qdict) | |
{ | |
int count = qdict_get_int(qdict, "count"); | |
int format = qdict_get_int(qdict, "format"); | |
int size = qdict_get_int(qdict, "size"); | |
target_long addr = qdict_get_int(qdict, "addr"); | |
memory_dump(mon, count, format, size, addr, 0); | |
} | |
static void hmp_physical_memory_dump(Monitor *mon, const QDict *qdict) | |
{ | |
int count = qdict_get_int(qdict, "count"); | |
int format = qdict_get_int(qdict, "format"); | |
int size = qdict_get_int(qdict, "size"); | |
hwaddr addr = qdict_get_int(qdict, "addr"); | |
memory_dump(mon, count, format, size, addr, 1); | |
} | |
static void hmp_physical_memory_access(Monitor *mon, const QDict *qdict) | |
{ | |
const char *path = qdict_get_str(qdict, "path"); | |
memory_access_start(path); | |
} | |
static void *gpa2hva(MemoryRegion **p_mr, hwaddr addr, Error **errp) | |
{ | |
MemoryRegionSection mrs = memory_region_find(get_system_memory(), | |
addr, 1); | |
if (!mrs.mr) { | |
error_setg(errp, "No memory is mapped at address 0x%" HWADDR_PRIx, addr); | |
return NULL; | |
} | |
if (!memory_region_is_ram(mrs.mr) && !memory_region_is_romd(mrs.mr)) { | |
error_setg(errp, "Memory at address 0x%" HWADDR_PRIx "is not RAM", addr); | |
memory_region_unref(mrs.mr); | |
return NULL; | |
} | |
*p_mr = mrs.mr; | |
return qemu_map_ram_ptr(mrs.mr->ram_block, mrs.offset_within_region); | |
} | |
static void hmp_gpa2hva(Monitor *mon, const QDict *qdict) | |
{ | |
hwaddr addr = qdict_get_int(qdict, "addr"); | |
Error *local_err = NULL; | |
MemoryRegion *mr = NULL; | |
void *ptr; | |
ptr = gpa2hva(&mr, addr, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
return; | |
} | |
monitor_printf(mon, "Host virtual address for 0x%" HWADDR_PRIx | |
" (%s) is %p\n", | |
addr, mr->name, ptr); | |
memory_region_unref(mr); | |
} | |
#ifdef CONFIG_LINUX | |
static uint64_t vtop(void *ptr, Error **errp) | |
{ | |
uint64_t pinfo; | |
uint64_t ret = -1; | |
uintptr_t addr = (uintptr_t) ptr; | |
uintptr_t pagesize = getpagesize(); | |
off_t offset = addr / pagesize * sizeof(pinfo); | |
int fd; | |
fd = open("/proc/self/pagemap", O_RDONLY); | |
if (fd == -1) { | |
error_setg_errno(errp, errno, "Cannot open /proc/self/pagemap"); | |
return -1; | |
} | |
/* Force copy-on-write if necessary. */ | |
atomic_add((uint8_t *)ptr, 0); | |
if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) { | |
error_setg_errno(errp, errno, "Cannot read pagemap"); | |
goto out; | |
} | |
if ((pinfo & (1ull << 63)) == 0) { | |
error_setg(errp, "Page not present"); | |
goto out; | |
} | |
ret = ((pinfo & 0x007fffffffffffffull) * pagesize) | (addr & (pagesize - 1)); | |
out: | |
close(fd); | |
return ret; | |
} | |
static void hmp_gpa2hpa(Monitor *mon, const QDict *qdict) | |
{ | |
hwaddr addr = qdict_get_int(qdict, "addr"); | |
Error *local_err = NULL; | |
MemoryRegion *mr = NULL; | |
void *ptr; | |
uint64_t physaddr; | |
ptr = gpa2hva(&mr, addr, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
return; | |
} | |
physaddr = vtop(ptr, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
} else { | |
monitor_printf(mon, "Host physical address for 0x%" HWADDR_PRIx | |
" (%s) is 0x%" PRIx64 "\n", | |
addr, mr->name, (uint64_t) physaddr); | |
} | |
memory_region_unref(mr); | |
} | |
#endif | |
static void do_print(Monitor *mon, const QDict *qdict) | |
{ | |
int format = qdict_get_int(qdict, "format"); | |
hwaddr val = qdict_get_int(qdict, "val"); | |
switch(format) { | |
case 'o': | |
monitor_printf(mon, "%#" HWADDR_PRIo, val); | |
break; | |
case 'x': | |
monitor_printf(mon, "%#" HWADDR_PRIx, val); | |
break; | |
case 'u': | |
monitor_printf(mon, "%" HWADDR_PRIu, val); | |
break; | |
default: | |
case 'd': | |
monitor_printf(mon, "%" HWADDR_PRId, val); | |
break; | |
case 'c': | |
monitor_printc(mon, val); | |
break; | |
} | |
monitor_printf(mon, "\n"); | |
} | |
static void hmp_sum(Monitor *mon, const QDict *qdict) | |
{ | |
uint32_t addr; | |
uint16_t sum; | |
uint32_t start = qdict_get_int(qdict, "start"); | |
uint32_t size = qdict_get_int(qdict, "size"); | |
sum = 0; | |
for(addr = start; addr < (start + size); addr++) { | |
uint8_t val = address_space_ldub(&address_space_memory, addr, | |
MEMTXATTRS_UNSPECIFIED, NULL); | |
/* BSD sum algorithm ('sum' Unix command) */ | |
sum = (sum >> 1) | (sum << 15); | |
sum += val; | |
} | |
monitor_printf(mon, "%05d\n", sum); | |
} | |
static int mouse_button_state; | |
static void hmp_mouse_move(Monitor *mon, const QDict *qdict) | |
{ | |
int dx, dy, dz, button; | |
const char *dx_str = qdict_get_str(qdict, "dx_str"); | |
const char *dy_str = qdict_get_str(qdict, "dy_str"); | |
const char *dz_str = qdict_get_try_str(qdict, "dz_str"); | |
dx = strtol(dx_str, NULL, 0); | |
dy = strtol(dy_str, NULL, 0); | |
qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx); | |
qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy); | |
if (dz_str) { | |
dz = strtol(dz_str, NULL, 0); | |
if (dz != 0) { | |
button = (dz > 0) ? INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; | |
qemu_input_queue_btn(NULL, button, true); | |
qemu_input_event_sync(); | |
qemu_input_queue_btn(NULL, button, false); | |
} | |
} | |
qemu_input_event_sync(); | |
} | |
static void hmp_mouse_button(Monitor *mon, const QDict *qdict) | |
{ | |
static uint32_t bmap[INPUT_BUTTON__MAX] = { | |
[INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, | |
[INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, | |
[INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, | |
}; | |
int button_state = qdict_get_int(qdict, "button_state"); | |
if (mouse_button_state == button_state) { | |
return; | |
} | |
qemu_input_update_buttons(NULL, bmap, mouse_button_state, button_state); | |
qemu_input_event_sync(); | |
mouse_button_state = button_state; | |
} | |
static void hmp_ioport_read(Monitor *mon, const QDict *qdict) | |
{ | |
int size = qdict_get_int(qdict, "size"); | |
int addr = qdict_get_int(qdict, "addr"); | |
int has_index = qdict_haskey(qdict, "index"); | |
uint32_t val; | |
int suffix; | |
if (has_index) { | |
int index = qdict_get_int(qdict, "index"); | |
cpu_outb(addr & IOPORTS_MASK, index & 0xff); | |
addr++; | |
} | |
addr &= 0xffff; | |
switch(size) { | |
default: | |
case 1: | |
val = cpu_inb(addr); | |
suffix = 'b'; | |
break; | |
case 2: | |
val = cpu_inw(addr); | |
suffix = 'w'; | |
break; | |
case 4: | |
val = cpu_inl(addr); | |
suffix = 'l'; | |
break; | |
} | |
monitor_printf(mon, "port%c[0x%04x] = %#0*x\n", | |
suffix, addr, size * 2, val); | |
} | |
static void hmp_ioport_write(Monitor *mon, const QDict *qdict) | |
{ | |
int size = qdict_get_int(qdict, "size"); | |
int addr = qdict_get_int(qdict, "addr"); | |
int val = qdict_get_int(qdict, "val"); | |
addr &= IOPORTS_MASK; | |
switch (size) { | |
default: | |
case 1: | |
cpu_outb(addr, val); | |
break; | |
case 2: | |
cpu_outw(addr, val); | |
break; | |
case 4: | |
cpu_outl(addr, val); | |
break; | |
} | |
} | |
static void hmp_boot_set(Monitor *mon, const QDict *qdict) | |
{ | |
Error *local_err = NULL; | |
const char *bootdevice = qdict_get_str(qdict, "bootdevice"); | |
qemu_boot_set(bootdevice, &local_err); | |
if (local_err) { | |
error_report_err(local_err); | |
} else { | |
monitor_printf(mon, "boot device list now set to %s\n", bootdevice); | |
} | |
} | |
static void hmp_info_mtree(Monitor *mon, const QDict *qdict) | |
{ | |
bool flatview = qdict_get_try_bool(qdict, "flatview", false); | |
bool dispatch_tree = qdict_get_try_bool(qdict, "dispatch_tree", false); | |
mtree_info((fprintf_function)monitor_printf, mon, flatview, dispatch_tree); | |
} | |
static void hmp_info_numa(Monitor *mon, const QDict *qdict) | |
{ | |
int i; | |
NumaNodeMem *node_mem; | |
CpuInfoList *cpu_list, *cpu; | |
cpu_list = qmp_query_cpus(&error_abort); | |
node_mem = g_new0(NumaNodeMem, nb_numa_nodes); | |
query_numa_node_mem(node_mem); | |
monitor_printf(mon, "%d nodes\n", nb_numa_nodes); | |
for (i = 0; i < nb_numa_nodes; i++) { | |
monitor_printf(mon, "node %d cpus:", i); | |
for (cpu = cpu_list; cpu; cpu = cpu->next) { | |
if (cpu->value->has_props && cpu->value->props->has_node_id && | |
cpu->value->props->node_id == i) { | |
monitor_printf(mon, " %" PRIi64, cpu->value->CPU); | |
} | |
} | |
monitor_printf(mon, "\n"); | |
monitor_printf(mon, "node %d size: %" PRId64 " MB\n", i, | |
node_mem[i].node_mem >> 20); | |
monitor_printf(mon, "node %d plugged: %" PRId64 " MB\n", i, | |
node_mem[i].node_plugged_mem >> 20); | |
} | |
qapi_free_CpuInfoList(cpu_list); | |
g_free(node_mem); | |
} | |
#ifdef CONFIG_PROFILER | |
int64_t tcg_time; | |
int64_t dev_time; | |
static void hmp_info_profile(Monitor *mon, const QDict *qdict) | |
{ | |
monitor_printf(mon, "async time %" PRId64 " (%0.3f)\n", | |
dev_time, dev_time / (double)NANOSECONDS_PER_SECOND); | |
monitor_printf(mon, "qemu time %" PRId64 " (%0.3f)\n", | |
tcg_time, tcg_time / (double)NANOSECONDS_PER_SECOND); | |
tcg_time = 0; | |
dev_time = 0; | |
} | |
#else | |
static void hmp_info_profile(Monitor *mon, const QDict *qdict) | |
{ | |
monitor_printf(mon, "Internal profiler not compiled\n"); | |
} | |
#endif | |
/* Capture support */ | |
static QLIST_HEAD (capture_list_head, CaptureState) capture_head; | |
static void hmp_info_capture(Monitor *mon, const QDict *qdict) | |
{ | |
int i; | |
CaptureState *s; | |
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) { | |
monitor_printf(mon, "[%d]: ", i); | |
s->ops.info (s->opaque); | |
} | |
} | |
static void hmp_stopcapture(Monitor *mon, const QDict *qdict) | |
{ | |
int i; | |
int n = qdict_get_int(qdict, "n"); | |
CaptureState *s; | |
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) { | |
if (i == n) { | |
s->ops.destroy (s->opaque); | |
QLIST_REMOVE (s, entries); | |
g_free (s); | |
return; | |
} | |
} | |
} | |
static void hmp_wavcapture(Monitor *mon, const QDict *qdict) | |
{ | |
const char *path = qdict_get_str(qdict, "path"); | |
int has_freq = qdict_haskey(qdict, "freq"); | |
int freq = qdict_get_try_int(qdict, "freq", -1); | |
int has_bits = qdict_haskey(qdict, "bits"); | |
int bits = qdict_get_try_int(qdict, "bits", -1); | |
int has_channels = qdict_haskey(qdict, "nchannels"); | |
int nchannels = qdict_get_try_int(qdict, "nchannels", -1); | |
CaptureState *s; | |
s = g_malloc0 (sizeof (*s)); | |
freq = has_freq ? freq : 44100; | |
bits = has_bits ? bits : 16; | |
nchannels = has_channels ? nchannels : 2; | |
if (wav_start_capture (s, path, freq, bits, nchannels)) { | |
monitor_printf(mon, "Failed to add wave capture\n"); | |
g_free (s); | |
return; | |
} | |
QLIST_INSERT_HEAD (&capture_head, s, entries); | |
} | |
static qemu_acl *find_acl(Monitor *mon, const char *name) | |
{ | |
qemu_acl *acl = qemu_acl_find(name); | |
if (!acl) { | |
monitor_printf(mon, "acl: unknown list '%s'\n", name); | |
} | |
return acl; | |
} | |
static void hmp_acl_show(Monitor *mon, const QDict *qdict) | |
{ | |
const char *aclname = qdict_get_str(qdict, "aclname"); | |
qemu_acl *acl = find_acl(mon, aclname); | |
qemu_acl_entry *entry; | |
int i = 0; | |
if (acl) { | |
monitor_printf(mon, "policy: %s\n", | |
acl->defaultDeny ? "deny" : "allow"); | |
QTAILQ_FOREACH(entry, &acl->entries, next) { | |
i++; | |
monitor_printf(mon, "%d: %s %s\n", i, | |
entry->deny ? "deny" : "allow", entry->match); | |
} | |
} | |
} | |
static void hmp_acl_reset(Monitor *mon, const QDict *qdict) | |
{ | |
const char *aclname = qdict_get_str(qdict, "aclname"); | |
qemu_acl *acl = find_acl(mon, aclname); | |
if (acl) { | |
qemu_acl_reset(acl); | |
monitor_printf(mon, "acl: removed all rules\n"); | |
} | |
} | |
static void hmp_acl_policy(Monitor *mon, const QDict *qdict) | |
{ | |
const char *aclname = qdict_get_str(qdict, "aclname"); | |
const char *policy = qdict_get_str(qdict, "policy"); | |
qemu_acl *acl = find_acl(mon, aclname); | |
if (acl) { | |
if (strcmp(policy, "allow") == 0) { | |
acl->defaultDeny = 0; | |
monitor_printf(mon, "acl: policy set to 'allow'\n"); | |
} else if (strcmp(policy, "deny") == 0) { | |
acl->defaultDeny = 1; | |
monitor_printf(mon, "acl: policy set to 'deny'\n"); | |
} else { | |
monitor_printf(mon, "acl: unknown policy '%s', " | |
"expected 'deny' or 'allow'\n", policy); | |
} | |
} | |
} | |
static void hmp_acl_add(Monitor *mon, const QDict *qdict) | |
{ | |
const char *aclname = qdict_get_str(qdict, "aclname"); | |
const char *match = qdict_get_str(qdict, "match"); | |
const char *policy = qdict_get_str(qdict, "policy"); | |
int has_index = qdict_haskey(qdict, "index"); | |
int index = qdict_get_try_int(qdict, "index", -1); | |
qemu_acl *acl = find_acl(mon, aclname); | |
int deny, ret; | |
if (acl) { | |
if (strcmp(policy, "allow") == 0) { | |
deny = 0; | |
} else if (strcmp(policy, "deny") == 0) { | |
deny = 1; | |
} else { | |
monitor_printf(mon, "acl: unknown policy '%s', " | |
"expected 'deny' or 'allow'\n", policy); | |
return; | |
} | |
if (has_index) | |
ret = qemu_acl_insert(acl, deny, match, index); | |
else | |
ret = qemu_acl_append(acl, deny, match); | |
if (ret < 0) | |
monitor_printf(mon, "acl: unable to add acl entry\n"); | |
else | |
monitor_printf(mon, "acl: added rule at position %d\n", ret); | |
} | |
} | |
static void hmp_acl_remove(Monitor *mon, const QDict *qdict) | |
{ | |
const char *aclname = qdict_get_str(qdict, "aclname"); | |
const char *match = qdict_get_str(qdict, "match"); | |
qemu_acl *acl = find_acl(mon, aclname); | |
int ret; | |
if (acl) { | |
ret = qemu_acl_remove(acl, match); | |
if (ret < 0) | |
monitor_printf(mon, "acl: no matching acl entry\n"); | |
else | |
monitor_printf(mon, "acl: removed rule at position %d\n", ret); | |
} | |
} | |
void qmp_getfd(const char *fdname, Error **errp) | |
{ | |
mon_fd_t *monfd; | |
int fd; | |
fd = qemu_chr_fe_get_msgfd(&cur_mon->chr); | |
if (fd == -1) { | |
error_setg(errp, QERR_FD_NOT_SUPPLIED); | |
return; | |
} | |
if (qemu_isdigit(fdname[0])) { | |
close(fd); | |
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdname", | |
"a name not starting with a digit"); | |
return; | |
} | |
QLIST_FOREACH(monfd, &cur_mon->fds, next) { | |
if (strcmp(monfd->name, fdname) != 0) { | |
continue; | |
} | |
close(monfd->fd); | |
monfd->fd = fd; | |
return; | |
} | |
monfd = g_malloc0(sizeof(mon_fd_t)); | |
monfd->name = g_strdup(fdname); | |
monfd->fd = fd; | |
QLIST_INSERT_HEAD(&cur_mon->fds, monfd, next); | |
} | |
void qmp_closefd(const char *fdname, Error **errp) | |
{ | |
mon_fd_t *monfd; | |
QLIST_FOREACH(monfd, &cur_mon->fds, next) { | |
if (strcmp(monfd->name, fdname) != 0) { | |
continue; | |
} | |
QLIST_REMOVE(monfd, next); | |
close(monfd->fd); | |
g_free(monfd->name); | |
g_free(monfd); | |
return; | |
} | |
error_setg(errp, QERR_FD_NOT_FOUND, fdname); | |
} | |
int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) | |
{ | |
mon_fd_t *monfd; | |
QLIST_FOREACH(monfd, &mon->fds, next) { | |
int fd; | |
if (strcmp(monfd->name, fdname) != 0) { | |
continue; | |
} | |
fd = monfd->fd; | |
/* caller takes ownership of fd */ | |
QLIST_REMOVE(monfd, next); | |
g_free(monfd->name); | |
g_free(monfd); | |
return fd; | |
} | |
error_setg(errp, "File descriptor named '%s' has not been found", fdname); | |
return -1; | |
} | |
static void monitor_fdset_cleanup(MonFdset *mon_fdset) | |
{ | |
MonFdsetFd *mon_fdset_fd; | |
MonFdsetFd *mon_fdset_fd_next; | |
QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) { | |
if ((mon_fdset_fd->removed || | |
(QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) && | |
runstate_is_running()) { | |
close(mon_fdset_fd->fd); | |
g_free(mon_fdset_fd->opaque); | |
QLIST_REMOVE(mon_fdset_fd, next); | |
g_free(mon_fdset_fd); | |
} | |
} | |
if (QLIST_EMPTY(&mon_fdset->fds) && QLIST_EMPTY(&mon_fdset->dup_fds)) { | |
QLIST_REMOVE(mon_fdset, next); | |
g_free(mon_fdset); | |
} | |
} | |
static void monitor_fdsets_cleanup(void) | |
{ | |
MonFdset *mon_fdset; | |
MonFdset *mon_fdset_next; | |
QLIST_FOREACH_SAFE(mon_fdset, &mon_fdsets, next, mon_fdset_next) { | |
monitor_fdset_cleanup(mon_fdset); | |
} | |
} | |
AddfdInfo *qmp_add_fd(bool has_fdset_id, int64_t fdset_id, bool has_opaque, | |
const char *opaque, Error **errp) | |
{ | |
int fd; | |
Monitor *mon = cur_mon; | |
AddfdInfo *fdinfo; | |
fd = qemu_chr_fe_get_msgfd(&mon->chr); | |
if (fd == -1) { | |
error_setg(errp, QERR_FD_NOT_SUPPLIED); | |
goto error; | |
} | |
fdinfo = monitor_fdset_add_fd(fd, has_fdset_id, fdset_id, | |
has_opaque, opaque, errp); | |
if (fdinfo) { | |
return fdinfo; | |
} | |
error: | |
if (fd != -1) { | |
close(fd); | |
} | |
return NULL; | |
} | |
void qmp_remove_fd(int64_t fdset_id, bool has_fd, int64_t fd, Error **errp) | |
{ | |
MonFdset *mon_fdset; | |
MonFdsetFd *mon_fdset_fd; | |
char fd_str[60]; | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
if (mon_fdset->id != fdset_id) { | |
continue; | |
} | |
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { | |
if (has_fd) { | |
if (mon_fdset_fd->fd != fd) { | |
continue; | |
} | |
mon_fdset_fd->removed = true; | |
break; | |
} else { | |
mon_fdset_fd->removed = true; | |
} | |
} | |
if (has_fd && !mon_fdset_fd) { | |
goto error; | |
} | |
monitor_fdset_cleanup(mon_fdset); | |
return; | |
} | |
error: | |
if (has_fd) { | |
snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64 ", fd:%" PRId64, | |
fdset_id, fd); | |
} else { | |
snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64, fdset_id); | |
} | |
error_setg(errp, QERR_FD_NOT_FOUND, fd_str); | |
} | |
FdsetInfoList *qmp_query_fdsets(Error **errp) | |
{ | |
MonFdset *mon_fdset; | |
MonFdsetFd *mon_fdset_fd; | |
FdsetInfoList *fdset_list = NULL; | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
FdsetInfoList *fdset_info = g_malloc0(sizeof(*fdset_info)); | |
FdsetFdInfoList *fdsetfd_list = NULL; | |
fdset_info->value = g_malloc0(sizeof(*fdset_info->value)); | |
fdset_info->value->fdset_id = mon_fdset->id; | |
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { | |
FdsetFdInfoList *fdsetfd_info; | |
fdsetfd_info = g_malloc0(sizeof(*fdsetfd_info)); | |
fdsetfd_info->value = g_malloc0(sizeof(*fdsetfd_info->value)); | |
fdsetfd_info->value->fd = mon_fdset_fd->fd; | |
if (mon_fdset_fd->opaque) { | |
fdsetfd_info->value->has_opaque = true; | |
fdsetfd_info->value->opaque = g_strdup(mon_fdset_fd->opaque); | |
} else { | |
fdsetfd_info->value->has_opaque = false; | |
} | |
fdsetfd_info->next = fdsetfd_list; | |
fdsetfd_list = fdsetfd_info; | |
} | |
fdset_info->value->fds = fdsetfd_list; | |
fdset_info->next = fdset_list; | |
fdset_list = fdset_info; | |
} | |
return fdset_list; | |
} | |
AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id, | |
bool has_opaque, const char *opaque, | |
Error **errp) | |
{ | |
MonFdset *mon_fdset = NULL; | |
MonFdsetFd *mon_fdset_fd; | |
AddfdInfo *fdinfo; | |
if (has_fdset_id) { | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
/* Break if match found or match impossible due to ordering by ID */ | |
if (fdset_id <= mon_fdset->id) { | |
if (fdset_id < mon_fdset->id) { | |
mon_fdset = NULL; | |
} | |
break; | |
} | |
} | |
} | |
if (mon_fdset == NULL) { | |
int64_t fdset_id_prev = -1; | |
MonFdset *mon_fdset_cur = QLIST_FIRST(&mon_fdsets); | |
if (has_fdset_id) { | |
if (fdset_id < 0) { | |
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdset-id", | |
"a non-negative value"); | |
return NULL; | |
} | |
/* Use specified fdset ID */ | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
mon_fdset_cur = mon_fdset; | |
if (fdset_id < mon_fdset_cur->id) { | |
break; | |
} | |
} | |
} else { | |
/* Use first available fdset ID */ | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
mon_fdset_cur = mon_fdset; | |
if (fdset_id_prev == mon_fdset_cur->id - 1) { | |
fdset_id_prev = mon_fdset_cur->id; | |
continue; | |
} | |
break; | |
} | |
} | |
mon_fdset = g_malloc0(sizeof(*mon_fdset)); | |
if (has_fdset_id) { | |
mon_fdset->id = fdset_id; | |
} else { | |
mon_fdset->id = fdset_id_prev + 1; | |
} | |
/* The fdset list is ordered by fdset ID */ | |
if (!mon_fdset_cur) { | |
QLIST_INSERT_HEAD(&mon_fdsets, mon_fdset, next); | |
} else if (mon_fdset->id < mon_fdset_cur->id) { | |
QLIST_INSERT_BEFORE(mon_fdset_cur, mon_fdset, next); | |
} else { | |
QLIST_INSERT_AFTER(mon_fdset_cur, mon_fdset, next); | |
} | |
} | |
mon_fdset_fd = g_malloc0(sizeof(*mon_fdset_fd)); | |
mon_fdset_fd->fd = fd; | |
mon_fdset_fd->removed = false; | |
if (has_opaque) { | |
mon_fdset_fd->opaque = g_strdup(opaque); | |
} | |
QLIST_INSERT_HEAD(&mon_fdset->fds, mon_fdset_fd, next); | |
fdinfo = g_malloc0(sizeof(*fdinfo)); | |
fdinfo->fdset_id = mon_fdset->id; | |
fdinfo->fd = mon_fdset_fd->fd; | |
return fdinfo; | |
} | |
int monitor_fdset_get_fd(int64_t fdset_id, int flags) | |
{ | |
#ifndef _WIN32 | |
MonFdset *mon_fdset; | |
MonFdsetFd *mon_fdset_fd; | |
int mon_fd_flags; | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
if (mon_fdset->id != fdset_id) { | |
continue; | |
} | |
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { | |
mon_fd_flags = fcntl(mon_fdset_fd->fd, F_GETFL); | |
if (mon_fd_flags == -1) { | |
return -1; | |
} | |
if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) { | |
return mon_fdset_fd->fd; | |
} | |
} | |
errno = EACCES; | |
return -1; | |
} | |
#endif | |
errno = ENOENT; | |
return -1; | |
} | |
int monitor_fdset_dup_fd_add(int64_t fdset_id, int dup_fd) | |
{ | |
MonFdset *mon_fdset; | |
MonFdsetFd *mon_fdset_fd_dup; | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
if (mon_fdset->id != fdset_id) { | |
continue; | |
} | |
QLIST_FOREACH(mon_fdset_fd_dup, &mon_fdset->dup_fds, next) { | |
if (mon_fdset_fd_dup->fd == dup_fd) { | |
return -1; | |
} | |
} | |
mon_fdset_fd_dup = g_malloc0(sizeof(*mon_fdset_fd_dup)); | |
mon_fdset_fd_dup->fd = dup_fd; | |
QLIST_INSERT_HEAD(&mon_fdset->dup_fds, mon_fdset_fd_dup, next); | |
return 0; | |
} | |
return -1; | |
} | |
static int monitor_fdset_dup_fd_find_remove(int dup_fd, bool remove) | |
{ | |
MonFdset *mon_fdset; | |
MonFdsetFd *mon_fdset_fd_dup; | |
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { | |
QLIST_FOREACH(mon_fdset_fd_dup, &mon_fdset->dup_fds, next) { | |
if (mon_fdset_fd_dup->fd == dup_fd) { | |
if (remove) { | |
QLIST_REMOVE(mon_fdset_fd_dup, next); | |
if (QLIST_EMPTY(&mon_fdset->dup_fds)) { | |
monitor_fdset_cleanup(mon_fdset); | |
} | |
return -1; | |
} else { | |
return mon_fdset->id; | |
} | |
} | |
} | |
} | |
return -1; | |
} | |
int monitor_fdset_dup_fd_find(int dup_fd) | |
{ | |
return monitor_fdset_dup_fd_find_remove(dup_fd, false); | |
} | |
void monitor_fdset_dup_fd_remove(int dup_fd) | |
{ | |
monitor_fdset_dup_fd_find_remove(dup_fd, true); | |
} | |
int monitor_fd_param(Monitor *mon, const char *fdname, Error **errp) | |
{ | |
int fd; | |
Error *local_err = NULL; | |
if (!qemu_isdigit(fdname[0]) && mon) { | |
fd = monitor_get_fd(mon, fdname, &local_err); | |
} else { | |
fd = qemu_parse_fd(fdname); | |
if (fd == -1) { | |
error_setg(&local_err, "Invalid file descriptor number '%s'", | |
fdname); | |
} | |
} | |
if (local_err) { | |
error_propagate(errp, local_err); | |
assert(fd == -1); | |
} else { | |
assert(fd != -1); | |
} | |
return fd; | |
} | |
/* Please update hmp-commands.hx when adding or changing commands */ | |
static mon_cmd_t info_cmds[] = { | |
#include "hmp-commands-info.h" | |
{ NULL, NULL, }, | |
}; | |
/* mon_cmds and info_cmds would be sorted at runtime */ | |
static mon_cmd_t mon_cmds[] = { | |
#include "hmp-commands.h" | |
{ NULL, NULL, }, | |
}; | |
/*******************************************************************/ | |
static const char *pch; | |
static sigjmp_buf expr_env; | |
static void GCC_FMT_ATTR(2, 3) QEMU_NORETURN | |
expr_error(Monitor *mon, const char *fmt, ...) | |
{ | |
va_list ap; | |
va_start(ap, fmt); | |
monitor_vprintf(mon, fmt, ap); | |
monitor_printf(mon, "\n"); | |
va_end(ap); | |
siglongjmp(expr_env, 1); | |
} | |
/* return 0 if OK, -1 if not found */ | |
static int get_monitor_def(target_long *pval, const char *name) | |
{ | |
const MonitorDef *md = target_monitor_defs(); | |
CPUState *cs = mon_get_cpu(); | |
void *ptr; | |
uint64_t tmp = 0; | |
int ret; | |
if (cs == NULL || md == NULL) { | |
return -1; | |
} | |
for(; md->name != NULL; md++) { | |
if (compare_cmd(name, md->name)) { | |
if (md->get_value) { | |
*pval = md->get_value(md, md->offset); | |
} else { | |
CPUArchState *env = mon_get_cpu_env(); | |
ptr = (uint8_t *)env + md->offset; | |
switch(md->type) { | |
case MD_I32: | |
*pval = *(int32_t *)ptr; | |
break; | |
case MD_TLONG: | |
*pval = *(target_long *)ptr; | |
break; | |
default: | |
*pval = 0; | |
break; | |
} | |
} | |
return 0; | |
} | |
} | |
ret = target_get_monitor_def(cs, name, &tmp); | |
if (!ret) { | |
*pval = (target_long) tmp; | |
} | |
return ret; | |
} | |
static void next(void) | |
{ | |
if (*pch != '\0') { | |
pch++; | |
while (qemu_isspace(*pch)) | |
pch++; | |
} | |
} | |
static int64_t expr_sum(Monitor *mon); | |
static int64_t expr_unary(Monitor *mon) | |
{ | |
int64_t n; | |
char *p; | |
int ret; | |
switch(*pch) { | |
case '+': | |
next(); | |
n = expr_unary(mon); | |
break; | |
case '-': | |
next(); | |
n = -expr_unary(mon); | |
break; | |
case '~': | |
next(); | |
n = ~expr_unary(mon); | |
break; | |
case '(': | |
next(); | |
n = expr_sum(mon); | |
if (*pch != ')') { | |
expr_error(mon, "')' expected"); | |
} | |
next(); | |
break; | |
case '\'': | |
pch++; | |
if (*pch == '\0') | |
expr_error(mon, "character constant expected"); | |
n = *pch; | |
pch++; | |
if (*pch != '\'') | |
expr_error(mon, "missing terminating \' character"); | |
next(); | |
break; | |
case '$': | |
{ | |
char buf[128], *q; | |
target_long reg=0; | |
pch++; | |
q = buf; | |
while ((*pch >= 'a' && *pch <= 'z') || | |
(*pch >= 'A' && *pch <= 'Z') || | |
(*pch >= '0' && *pch <= '9') || | |
*pch == '_' || *pch == '.') { | |
if ((q - buf) < sizeof(buf) - 1) | |
*q++ = *pch; | |
pch++; | |
} | |
while (qemu_isspace(*pch)) | |
pch++; | |
*q = 0; | |
ret = get_monitor_def(®, buf); | |
if (ret < 0) | |
expr_error(mon, "unknown register"); | |
n = reg; | |
} | |
break; | |
case '\0': | |
expr_error(mon, "unexpected end of expression"); | |
n = 0; | |
break; | |
default: | |
errno = 0; | |
n = strtoull(pch, &p, 0); | |
if (errno == ERANGE) { | |
expr_error(mon, "number too large"); | |
} | |
if (pch == p) { | |
expr_error(mon, "invalid char '%c' in expression", *p); | |
} | |
pch = p; | |
while (qemu_isspace(*pch)) | |
pch++; | |
break; | |
} | |
return n; | |
} | |
static int64_t expr_prod(Monitor *mon) | |
{ | |
int64_t val, val2; | |
int op; | |
val = expr_unary(mon); | |
for(;;) { | |
op = *pch; | |
if (op != '*' && op != '/' && op != '%') | |
break; | |
next(); | |
val2 = expr_unary(mon); | |
switch(op) { | |
default: | |
case '*': | |
val *= val2; | |
break; | |
case '/': | |
case '%': | |
if (val2 == 0) | |
expr_error(mon, "division by zero"); | |
if (op == '/') | |
val /= val2; | |
else | |
val %= val2; | |
break; | |
} | |
} | |
return val; | |
} | |
static int64_t expr_logic(Monitor *mon) | |
{ | |
int64_t val, val2; | |
int op; | |
val = expr_prod(mon); | |
for(;;) { | |
op = *pch; | |
if (op != '&' && op != '|' && op != '^') | |
break; | |
next(); | |
val2 = expr_prod(mon); | |
switch(op) { | |
default: | |
case '&': | |
val &= val2; | |
break; | |
case '|': | |
val |= val2; | |
break; | |
case '^': | |
val ^= val2; | |
break; | |
} | |
} | |
return val; | |
} | |
static int64_t expr_sum(Monitor *mon) | |
{ | |
int64_t val, val2; | |
int op; | |
val = expr_logic(mon); | |
for(;;) { | |
op = *pch; | |
if (op != '+' && op != '-') | |
break; | |
next(); | |
val2 = expr_logic(mon); | |
if (op == '+') | |
val += val2; | |
else | |
val -= val2; | |
} | |
return val; | |
} | |
static int get_expr(Monitor *mon, int64_t *pval, const char **pp) | |
{ | |
pch = *pp; | |
if (sigsetjmp(expr_env, 0)) { | |
*pp = pch; | |
return -1; | |
} | |
while (qemu_isspace(*pch)) | |
pch++; | |
*pval = expr_sum(mon); | |
*pp = pch; | |
return 0; | |
} | |
static int get_double(Monitor *mon, double *pval, const char **pp) | |
{ | |
const char *p = *pp; | |
char *tailp; | |
double d; | |
d = strtod(p, &tailp); | |
if (tailp == p) { | |
monitor_printf(mon, "Number expected\n"); | |
return -1; | |
} | |
if (d != d || d - d != 0) { | |
/* NaN or infinity */ | |
monitor_printf(mon, "Bad number\n"); | |
return -1; | |
} | |
*pval = d; | |
*pp = tailp; | |
return 0; | |
} | |
/* | |
* Store the command-name in cmdname, and return a pointer to | |
* the remaining of the command string. | |
*/ | |
static const char *get_command_name(const char *cmdline, | |
char *cmdname, size_t nlen) | |
{ | |
size_t len; | |
const char *p, *pstart; | |
p = cmdline; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*p == '\0') | |
return NULL; | |
pstart = p; | |
while (*p != '\0' && *p != '/' && !qemu_isspace(*p)) | |
p++; | |
len = p - pstart; | |
if (len > nlen - 1) | |
len = nlen - 1; | |
memcpy(cmdname, pstart, len); | |
cmdname[len] = '\0'; | |
return p; | |
} | |
/** | |
* Read key of 'type' into 'key' and return the current | |
* 'type' pointer. | |
*/ | |
static char *key_get_info(const char *type, char **key) | |
{ | |
size_t len; | |
char *p, *str; | |
if (*type == ',') | |
type++; | |
p = strchr(type, ':'); | |
if (!p) { | |
*key = NULL; | |
return NULL; | |
} | |
len = p - type; | |
str = g_malloc(len + 1); | |
memcpy(str, type, len); | |
str[len] = '\0'; | |
*key = str; | |
return ++p; | |
} | |
static int default_fmt_format = 'x'; | |
static int default_fmt_size = 4; | |
static int is_valid_option(const char *c, const char *typestr) | |
{ | |
char option[3]; | |
option[0] = '-'; | |
option[1] = *c; | |
option[2] = '\0'; | |
typestr = strstr(typestr, option); | |
return (typestr != NULL); | |
} | |
static const mon_cmd_t *search_dispatch_table(const mon_cmd_t *disp_table, | |
const char *cmdname) | |
{ | |
const mon_cmd_t *cmd; | |
for (cmd = disp_table; cmd->name != NULL; cmd++) { | |
if (compare_cmd(cmdname, cmd->name)) { | |
return cmd; | |
} | |
} | |
return NULL; | |
} | |
/* | |
* Parse command name from @cmdp according to command table @table. | |
* If blank, return NULL. | |
* Else, if no valid command can be found, report to @mon, and return | |
* NULL. | |
* Else, change @cmdp to point right behind the name, and return its | |
* command table entry. | |
* Do not assume the return value points into @table! It doesn't when | |
* the command is found in a sub-command table. | |
*/ | |
static const mon_cmd_t *monitor_parse_command(Monitor *mon, | |
const char *cmdp_start, | |
const char **cmdp, | |
mon_cmd_t *table) | |
{ | |
const char *p; | |
const mon_cmd_t *cmd; | |
char cmdname[256]; | |
/* extract the command name */ | |
p = get_command_name(*cmdp, cmdname, sizeof(cmdname)); | |
if (!p) | |
return NULL; | |
cmd = search_dispatch_table(table, cmdname); | |
if (!cmd) { | |
monitor_printf(mon, "unknown command: '%.*s'\n", | |
(int)(p - cmdp_start), cmdp_start); | |
return NULL; | |
} | |
/* filter out following useless space */ | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
*cmdp = p; | |
/* search sub command */ | |
if (cmd->sub_table != NULL && *p != '\0') { | |
return monitor_parse_command(mon, cmdp_start, cmdp, cmd->sub_table); | |
} | |
return cmd; | |
} | |
/* | |
* Parse arguments for @cmd. | |
* If it can't be parsed, report to @mon, and return NULL. | |
* Else, insert command arguments into a QDict, and return it. | |
* Note: On success, caller has to free the QDict structure. | |
*/ | |
static QDict *monitor_parse_arguments(Monitor *mon, | |
const char **endp, | |
const mon_cmd_t *cmd) | |
{ | |
const char *typestr; | |
char *key; | |
int c; | |
const char *p = *endp; | |
char buf[1024]; | |
QDict *qdict = qdict_new(); | |
/* parse the parameters */ | |
typestr = cmd->args_type; | |
for(;;) { | |
typestr = key_get_info(typestr, &key); | |
if (!typestr) | |
break; | |
c = *typestr; | |
typestr++; | |
switch(c) { | |
case 'F': | |
case 'B': | |
case 's': | |
{ | |
int ret; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*typestr == '?') { | |
typestr++; | |
if (*p == '\0') { | |
/* no optional string: NULL argument */ | |
break; | |
} | |
} | |
ret = get_str(buf, sizeof(buf), &p); | |
if (ret < 0) { | |
switch(c) { | |
case 'F': | |
monitor_printf(mon, "%s: filename expected\n", | |
cmd->name); | |
break; | |
case 'B': | |
monitor_printf(mon, "%s: block device name expected\n", | |
cmd->name); | |
break; | |
default: | |
monitor_printf(mon, "%s: string expected\n", cmd->name); | |
break; | |
} | |
goto fail; | |
} | |
qdict_put_str(qdict, key, buf); | |
} | |
break; | |
case 'O': | |
{ | |
QemuOptsList *opts_list; | |
QemuOpts *opts; | |
opts_list = qemu_find_opts(key); | |
if (!opts_list || opts_list->desc->name) { | |
goto bad_type; | |
} | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
if (!*p) | |
break; | |
if (get_str(buf, sizeof(buf), &p) < 0) { | |
goto fail; | |
} | |
opts = qemu_opts_parse_noisily(opts_list, buf, true); | |
if (!opts) { | |
goto fail; | |
} | |
qemu_opts_to_qdict(opts, qdict); | |
qemu_opts_del(opts); | |
} | |
break; | |
case '/': | |
{ | |
int count, format, size; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*p == '/') { | |
/* format found */ | |
p++; | |
count = 1; | |
if (qemu_isdigit(*p)) { | |
count = 0; | |
while (qemu_isdigit(*p)) { | |
count = count * 10 + (*p - '0'); | |
p++; | |
} | |
} | |
size = -1; | |
format = -1; | |
for(;;) { | |
switch(*p) { | |
case 'o': | |
case 'd': | |
case 'u': | |
case 'x': | |
case 'i': | |
case 'c': | |
format = *p++; | |
break; | |
case 'b': | |
size = 1; | |
p++; | |
break; | |
case 'h': | |
size = 2; | |
p++; | |
break; | |
case 'w': | |
size = 4; | |
p++; | |
break; | |
case 'g': | |
case 'L': | |
size = 8; | |
p++; | |
break; | |
default: | |
goto next; | |
} | |
} | |
next: | |
if (*p != '\0' && !qemu_isspace(*p)) { | |
monitor_printf(mon, "invalid char in format: '%c'\n", | |
*p); | |
goto fail; | |
} | |
if (format < 0) | |
format = default_fmt_format; | |
if (format != 'i') { | |
/* for 'i', not specifying a size gives -1 as size */ | |
if (size < 0) | |
size = default_fmt_size; | |
default_fmt_size = size; | |
} | |
default_fmt_format = format; | |
} else { | |
count = 1; | |
format = default_fmt_format; | |
if (format != 'i') { | |
size = default_fmt_size; | |
} else { | |
size = -1; | |
} | |
} | |
qdict_put_int(qdict, "count", count); | |
qdict_put_int(qdict, "format", format); | |
qdict_put_int(qdict, "size", size); | |
} | |
break; | |
case 'i': | |
case 'l': | |
case 'M': | |
{ | |
int64_t val; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*typestr == '?' || *typestr == '.') { | |
if (*typestr == '?') { | |
if (*p == '\0') { | |
typestr++; | |
break; | |
} | |
} else { | |
if (*p == '.') { | |
p++; | |
while (qemu_isspace(*p)) | |
p++; | |
} else { | |
typestr++; | |
break; | |
} | |
} | |
typestr++; | |
} | |
if (get_expr(mon, &val, &p)) | |
goto fail; | |
/* Check if 'i' is greater than 32-bit */ | |
if ((c == 'i') && ((val >> 32) & 0xffffffff)) { | |
monitor_printf(mon, "\'%s\' has failed: ", cmd->name); | |
monitor_printf(mon, "integer is for 32-bit values\n"); | |
goto fail; | |
} else if (c == 'M') { | |
if (val < 0) { | |
monitor_printf(mon, "enter a positive value\n"); | |
goto fail; | |
} | |
val <<= 20; | |
} | |
qdict_put_int(qdict, key, val); | |
} | |
break; | |
case 'o': | |
{ | |
int ret; | |
uint64_t val; | |
char *end; | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
if (*typestr == '?') { | |
typestr++; | |
if (*p == '\0') { | |
break; | |
} | |
} | |
ret = qemu_strtosz_MiB(p, &end, &val); | |
if (ret < 0 || val > INT64_MAX) { | |
monitor_printf(mon, "invalid size\n"); | |
goto fail; | |
} | |
qdict_put_int(qdict, key, val); | |
p = end; | |
} | |
break; | |
case 'T': | |
{ | |
double val; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*typestr == '?') { | |
typestr++; | |
if (*p == '\0') { | |
break; | |
} | |
} | |
if (get_double(mon, &val, &p) < 0) { | |
goto fail; | |
} | |
if (p[0] && p[1] == 's') { | |
switch (*p) { | |
case 'm': | |
val /= 1e3; p += 2; break; | |
case 'u': | |
val /= 1e6; p += 2; break; | |
case 'n': | |
val /= 1e9; p += 2; break; | |
} | |
} | |
if (*p && !qemu_isspace(*p)) { | |
monitor_printf(mon, "Unknown unit suffix\n"); | |
goto fail; | |
} | |
qdict_put(qdict, key, qnum_from_double(val)); | |
} | |
break; | |
case 'b': | |
{ | |
const char *beg; | |
bool val; | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
beg = p; | |
while (qemu_isgraph(*p)) { | |
p++; | |
} | |
if (p - beg == 2 && !memcmp(beg, "on", p - beg)) { | |
val = true; | |
} else if (p - beg == 3 && !memcmp(beg, "off", p - beg)) { | |
val = false; | |
} else { | |
monitor_printf(mon, "Expected 'on' or 'off'\n"); | |
goto fail; | |
} | |
qdict_put_bool(qdict, key, val); | |
} | |
break; | |
case '-': | |
{ | |
const char *tmp = p; | |
int skip_key = 0; | |
/* option */ | |
c = *typestr++; | |
if (c == '\0') | |
goto bad_type; | |
while (qemu_isspace(*p)) | |
p++; | |
if (*p == '-') { | |
p++; | |
if(c != *p) { | |
if(!is_valid_option(p, typestr)) { | |
monitor_printf(mon, "%s: unsupported option -%c\n", | |
cmd->name, *p); | |
goto fail; | |
} else { | |
skip_key = 1; | |
} | |
} | |
if(skip_key) { | |
p = tmp; | |
} else { | |
/* has option */ | |
p++; | |
qdict_put_bool(qdict, key, true); | |
} | |
} | |
} | |
break; | |
case 'S': | |
{ | |
/* package all remaining string */ | |
int len; | |
while (qemu_isspace(*p)) { | |
p++; | |
} | |
if (*typestr == '?') { | |
typestr++; | |
if (*p == '\0') { | |
/* no remaining string: NULL argument */ | |
break; | |
} | |
} | |
len = strlen(p); | |
if (len <= 0) { | |
monitor_printf(mon, "%s: string expected\n", | |
cmd->name); | |
goto fail; | |
} | |
qdict_put_str(qdict, key, p); | |
p += len; | |
} | |
break; | |
default: | |
bad_type: | |
monitor_printf(mon, "%s: unknown type '%c'\n", cmd->name, c); | |
goto fail; | |
} | |
g_free(key); | |
key = NULL; | |
} | |
/* check that all arguments were parsed */ | |
while (qemu_isspace(*p)) | |
p++; | |
if (*p != '\0') { | |
monitor_printf(mon, "%s: extraneous characters at the end of line\n", | |
cmd->name); | |
goto fail; | |
} | |
return qdict; | |
fail: | |
QDECREF(qdict); | |
g_free(key); | |
return NULL; | |
} | |
static void handle_hmp_command(Monitor *mon, const char *cmdline) | |
{ | |
QDict *qdict; | |
const mon_cmd_t *cmd; | |
trace_handle_hmp_command(mon, cmdline); | |
cmd = monitor_parse_command(mon, cmdline, &cmdline, mon->cmd_table); | |
if (!cmd) { | |
return; | |
} | |
qdict = monitor_parse_arguments(mon, &cmdline, cmd); | |
if (!qdict) { | |
monitor_printf(mon, "Try \"help %s\" for more information\n", | |
cmd->name); | |
return; | |
} | |
cmd->cmd(mon, qdict); | |
QDECREF(qdict); | |
} | |
static void cmd_completion(Monitor *mon, const char *name, const char *list) | |
{ | |
const char *p, *pstart; | |
char cmd[128]; | |
int len; | |
p = list; | |
for(;;) { | |
pstart = p; | |
p = strchr(p, '|'); | |
if (!p) | |
p = pstart + strlen(pstart); | |
len = p - pstart; | |
if (len > sizeof(cmd) - 2) | |
len = sizeof(cmd) - 2; | |
memcpy(cmd, pstart, len); | |
cmd[len] = '\0'; | |
if (name[0] == '\0' || !strncmp(name, cmd, strlen(name))) { | |
readline_add_completion(mon->rs, cmd); | |
} | |
if (*p == '\0') | |
break; | |
p++; | |
} | |
} | |
static void file_completion(Monitor *mon, const char *input) | |
{ | |
DIR *ffs; | |
struct dirent *d; | |
char path[1024]; | |
char file[1024], file_prefix[1024]; | |
int input_path_len; | |
const char *p; | |
p = strrchr(input, '/'); | |
if (!p) { | |
input_path_len = 0; | |
pstrcpy(file_prefix, sizeof(file_prefix), input); | |
pstrcpy(path, sizeof(path), "."); | |
} else { | |
input_path_len = p - input + 1; | |
memcpy(path, input, input_path_len); | |
if (input_path_len > sizeof(path) - 1) | |
input_path_len = sizeof(path) - 1; | |
path[input_path_len] = '\0'; | |
pstrcpy(file_prefix, sizeof(file_prefix), p + 1); | |
} | |
ffs = opendir(path); | |
if (!ffs) | |
return; | |
for(;;) { | |
struct stat sb; | |
d = readdir(ffs); | |
if (!d) | |
break; | |
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) { | |
continue; | |
} | |
if (strstart(d->d_name, file_prefix, NULL)) { | |
memcpy(file, input, input_path_len); | |
if (input_path_len < sizeof(file)) | |
pstrcpy(file + input_path_len, sizeof(file) - input_path_len, | |
d->d_name); | |
/* stat the file to find out if it's a directory. | |
* In that case add a slash to speed up typing long paths | |
*/ | |
if (stat(file, &sb) == 0 && S_ISDIR(sb.st_mode)) { | |
pstrcat(file, sizeof(file), "/"); | |
} | |
readline_add_completion(mon->rs, file); | |
} | |
} | |
closedir(ffs); | |
} | |
static const char *next_arg_type(const char *typestr) | |
{ | |
const char *p = strchr(typestr, ':'); | |
return (p != NULL ? ++p : typestr); | |
} | |
static void add_completion_option(ReadLineState *rs, const char *str, | |
const char *option) | |
{ | |
if (!str || !option) { | |
return; | |
} | |
if (!strncmp(option, str, strlen(str))) { | |
readline_add_completion(rs, option); | |
} | |
} | |
void chardev_add_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
ChardevBackendInfoList *list, *start; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
start = list = qmp_query_chardev_backends(NULL); | |
while (list) { | |
const char *chr_name = list->value->name; | |
if (!strncmp(chr_name, str, len)) { | |
readline_add_completion(rs, chr_name); | |
} | |
list = list->next; | |
} | |
qapi_free_ChardevBackendInfoList(start); | |
} | |
void netdev_add_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
int i; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
for (i = 0; i < NET_CLIENT_DRIVER__MAX; i++) { | |
add_completion_option(rs, str, NetClientDriver_str(i)); | |
} | |
} | |
void device_add_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
GSList *list, *elt; | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
list = elt = object_class_get_list(TYPE_DEVICE, false); | |
while (elt) { | |
const char *name; | |
DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data, | |
TYPE_DEVICE); | |
name = object_class_get_name(OBJECT_CLASS(dc)); | |
if (dc->user_creatable | |
&& !strncmp(name, str, len)) { | |
readline_add_completion(rs, name); | |
} | |
elt = elt->next; | |
} | |
g_slist_free(list); | |
} | |
void object_add_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
GSList *list, *elt; | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
list = elt = object_class_get_list(TYPE_USER_CREATABLE, false); | |
while (elt) { | |
const char *name; | |
name = object_class_get_name(OBJECT_CLASS(elt->data)); | |
if (!strncmp(name, str, len) && strcmp(name, TYPE_USER_CREATABLE)) { | |
readline_add_completion(rs, name); | |
} | |
elt = elt->next; | |
} | |
g_slist_free(list); | |
} | |
static void peripheral_device_del_completion(ReadLineState *rs, | |
const char *str, size_t len) | |
{ | |
Object *peripheral = container_get(qdev_get_machine(), "/peripheral"); | |
GSList *list, *item; | |
list = qdev_build_hotpluggable_device_list(peripheral); | |
if (!list) { | |
return; | |
} | |
for (item = list; item; item = g_slist_next(item)) { | |
DeviceState *dev = item->data; | |
if (dev->id && !strncmp(str, dev->id, len)) { | |
readline_add_completion(rs, dev->id); | |
} | |
} | |
g_slist_free(list); | |
} | |
void chardev_remove_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
ChardevInfoList *list, *start; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
start = list = qmp_query_chardev(NULL); | |
while (list) { | |
ChardevInfo *chr = list->value; | |
if (!strncmp(chr->label, str, len)) { | |
readline_add_completion(rs, chr->label); | |
} | |
list = list->next; | |
} | |
qapi_free_ChardevInfoList(start); | |
} | |
static void ringbuf_completion(ReadLineState *rs, const char *str) | |
{ | |
size_t len; | |
ChardevInfoList *list, *start; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
start = list = qmp_query_chardev(NULL); | |
while (list) { | |
ChardevInfo *chr_info = list->value; | |
if (!strncmp(chr_info->label, str, len)) { | |
Chardev *chr = qemu_chr_find(chr_info->label); | |
if (chr && CHARDEV_IS_RINGBUF(chr)) { | |
readline_add_completion(rs, chr_info->label); | |
} | |
} | |
list = list->next; | |
} | |
qapi_free_ChardevInfoList(start); | |
} | |
void ringbuf_write_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
if (nb_args != 2) { | |
return; | |
} | |
ringbuf_completion(rs, str); | |
} | |
void device_del_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
peripheral_device_del_completion(rs, str, len); | |
} | |
void object_del_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
ObjectPropertyInfoList *list, *start; | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
start = list = qmp_qom_list("/objects", NULL); | |
while (list) { | |
ObjectPropertyInfo *info = list->value; | |
if (!strncmp(info->type, "child<", 5) | |
&& !strncmp(info->name, str, len)) { | |
readline_add_completion(rs, info->name); | |
} | |
list = list->next; | |
} | |
qapi_free_ObjectPropertyInfoList(start); | |
} | |
void sendkey_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
int i; | |
char *sep; | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
sep = strrchr(str, '-'); | |
if (sep) { | |
str = sep + 1; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
for (i = 0; i < Q_KEY_CODE__MAX; i++) { | |
if (!strncmp(str, QKeyCode_str(i), len)) { | |
readline_add_completion(rs, QKeyCode_str(i)); | |
} | |
} | |
} | |
void set_link_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
NetClientState *ncs[MAX_QUEUE_NUM]; | |
int count, i; | |
count = qemu_find_net_clients_except(NULL, ncs, | |
NET_CLIENT_DRIVER_NONE, | |
MAX_QUEUE_NUM); | |
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) { | |
const char *name = ncs[i]->name; | |
if (!strncmp(str, name, len)) { | |
readline_add_completion(rs, name); | |
} | |
} | |
} else if (nb_args == 3) { | |
add_completion_option(rs, str, "on"); | |
add_completion_option(rs, str, "off"); | |
} | |
} | |
void netdev_del_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
int len, count, i; | |
NetClientState *ncs[MAX_QUEUE_NUM]; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
count = qemu_find_net_clients_except(NULL, ncs, NET_CLIENT_DRIVER_NIC, | |
MAX_QUEUE_NUM); | |
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) { | |
QemuOpts *opts; | |
const char *name = ncs[i]->name; | |
if (strncmp(str, name, len)) { | |
continue; | |
} | |
opts = qemu_opts_find(qemu_find_opts_err("netdev", NULL), name); | |
if (opts) { | |
readline_add_completion(rs, name); | |
} | |
} | |
} | |
void info_trace_events_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
TraceEventIter iter; | |
TraceEvent *ev; | |
char *pattern = g_strdup_printf("%s*", str); | |
trace_event_iter_init(&iter, pattern); | |
while ((ev = trace_event_iter_next(&iter)) != NULL) { | |
readline_add_completion(rs, trace_event_get_name(ev)); | |
} | |
g_free(pattern); | |
} | |
} | |
void trace_event_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
size_t len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
TraceEventIter iter; | |
TraceEvent *ev; | |
char *pattern = g_strdup_printf("%s*", str); | |
trace_event_iter_init(&iter, pattern); | |
while ((ev = trace_event_iter_next(&iter)) != NULL) { | |
readline_add_completion(rs, trace_event_get_name(ev)); | |
} | |
g_free(pattern); | |
} else if (nb_args == 3) { | |
add_completion_option(rs, str, "on"); | |
add_completion_option(rs, str, "off"); | |
} | |
} | |
void watchdog_action_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
int i; | |
if (nb_args != 2) { | |
return; | |
} | |
readline_set_completion_index(rs, strlen(str)); | |
for (i = 0; i < WATCHDOG_ACTION__MAX; i++) { | |
add_completion_option(rs, str, WatchdogAction_str(i)); | |
} | |
} | |
void migrate_set_capability_completion(ReadLineState *rs, int nb_args, | |
const char *str) | |
{ | |
size_t len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
int i; | |
for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) { | |
const char *name = MigrationCapability_str(i); | |
if (!strncmp(str, name, len)) { | |
readline_add_completion(rs, name); | |
} | |
} | |
} else if (nb_args == 3) { | |
add_completion_option(rs, str, "on"); | |
add_completion_option(rs, str, "off"); | |
} | |
} | |
void migrate_set_parameter_completion(ReadLineState *rs, int nb_args, | |
const char *str) | |
{ | |
size_t len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
int i; | |
for (i = 0; i < MIGRATION_PARAMETER__MAX; i++) { | |
const char *name = MigrationParameter_str(i); | |
if (!strncmp(str, name, len)) { | |
readline_add_completion(rs, name); | |
} | |
} | |
} | |
} | |
void host_net_add_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
int i; | |
size_t len; | |
if (nb_args != 2) { | |
return; | |
} | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
for (i = 0; host_net_devices[i]; i++) { | |
if (!strncmp(host_net_devices[i], str, len)) { | |
readline_add_completion(rs, host_net_devices[i]); | |
} | |
} | |
} | |
void host_net_remove_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
NetClientState *ncs[MAX_QUEUE_NUM]; | |
int count, i, len; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
if (nb_args == 2) { | |
count = qemu_find_net_clients_except(NULL, ncs, | |
NET_CLIENT_DRIVER_NONE, | |
MAX_QUEUE_NUM); | |
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) { | |
int id; | |
char name[16]; | |
if (net_hub_id_for_client(ncs[i], &id)) { | |
continue; | |
} | |
snprintf(name, sizeof(name), "%d", id); | |
if (!strncmp(str, name, len)) { | |
readline_add_completion(rs, name); | |
} | |
} | |
return; | |
} else if (nb_args == 3) { | |
count = qemu_find_net_clients_except(NULL, ncs, | |
NET_CLIENT_DRIVER_NIC, | |
MAX_QUEUE_NUM); | |
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) { | |
int id; | |
const char *name; | |
if (ncs[i]->info->type == NET_CLIENT_DRIVER_HUBPORT || | |
net_hub_id_for_client(ncs[i], &id)) { | |
continue; | |
} | |
name = ncs[i]->name; | |
if (!strncmp(str, name, len)) { | |
readline_add_completion(rs, name); | |
} | |
} | |
return; | |
} | |
} | |
static void vm_completion(ReadLineState *rs, const char *str) | |
{ | |
size_t len; | |
BlockDriverState *bs; | |
BdrvNextIterator it; | |
len = strlen(str); | |
readline_set_completion_index(rs, len); | |
for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) { | |
SnapshotInfoList *snapshots, *snapshot; | |
AioContext *ctx = bdrv_get_aio_context(bs); | |
bool ok = false; | |
aio_context_acquire(ctx); | |
if (bdrv_can_snapshot(bs)) { | |
ok = bdrv_query_snapshot_info_list(bs, &snapshots, NULL) == 0; | |
} | |
aio_context_release(ctx); | |
if (!ok) { | |
continue; | |
} | |
snapshot = snapshots; | |
while (snapshot) { | |
char *completion = snapshot->value->name; | |
if (!strncmp(str, completion, len)) { | |
readline_add_completion(rs, completion); | |
} | |
completion = snapshot->value->id; | |
if (!strncmp(str, completion, len)) { | |
readline_add_completion(rs, completion); | |
} | |
snapshot = snapshot->next; | |
} | |
qapi_free_SnapshotInfoList(snapshots); | |
} | |
} | |
void delvm_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
if (nb_args == 2) { | |
vm_completion(rs, str); | |
} | |
} | |
void loadvm_completion(ReadLineState *rs, int nb_args, const char *str) | |
{ | |
if (nb_args == 2) { | |
vm_completion(rs, str); | |
} | |
} | |
static void monitor_find_completion_by_table(Monitor *mon, | |
const mon_cmd_t *cmd_table, | |
char **args, | |
int nb_args) | |
{ | |
const char *cmdname; | |
int i; | |
const char *ptype, *str, *name; | |
const mon_cmd_t *cmd; | |
BlockBackend *blk = NULL; | |
if (nb_args <= 1) { | |
/* command completion */ | |
if (nb_args == 0) | |
cmdname = ""; | |
else | |
cmdname = args[0]; | |
readline_set_completion_index(mon->rs, strlen(cmdname)); | |
for (cmd = cmd_table; cmd->name != NULL; cmd++) { | |
cmd_completion(mon, cmdname, cmd->name); | |
} | |
} else { | |
/* find the command */ | |
for (cmd = cmd_table; cmd->name != NULL; cmd++) { | |
if (compare_cmd(args[0], cmd->name)) { | |
break; | |
} | |
} | |
if (!cmd->name) { | |
return; | |
} | |
if (cmd->sub_table) { | |
/* do the job again */ | |
monitor_find_completion_by_table(mon, cmd->sub_table, | |
&args[1], nb_args - 1); | |
return; | |
} | |
if (cmd->command_completion) { | |
cmd->command_completion(mon->rs, nb_args, args[nb_args - 1]); | |
return; | |
} | |
ptype = next_arg_type(cmd->args_type); | |
for(i = 0; i < nb_args - 2; i++) { | |
if (*ptype != '\0') { | |
ptype = next_arg_type(ptype); | |
while (*ptype == '?') | |
ptype = next_arg_type(ptype); | |
} | |
} | |
str = args[nb_args - 1]; | |
while (*ptype == '-' && ptype[1] != '\0') { | |
ptype = next_arg_type(ptype); | |
} | |
switch(*ptype) { | |
case 'F': | |
/* file completion */ | |
readline_set_completion_index(mon->rs, strlen(str)); | |
file_completion(mon, str); | |
break; | |
case 'B': | |
/* block device name completion */ | |
readline_set_completion_index(mon->rs, strlen(str)); | |
while ((blk = blk_next(blk)) != NULL) { | |
name = blk_name(blk); | |
if (str[0] == '\0' || | |
!strncmp(name, str, strlen(str))) { | |
readline_add_completion(mon->rs, name); | |
} | |
} | |
break; | |
case 's': | |
case 'S': | |
if (!strcmp(cmd->name, "help|?")) { | |
monitor_find_completion_by_table(mon, cmd_table, | |
&args[1], nb_args - 1); | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
} | |
static void monitor_find_completion(void *opaque, | |
const char *cmdline) | |
{ | |
Monitor *mon = opaque; | |
char *args[MAX_ARGS]; | |
int nb_args, len; | |
/* 1. parse the cmdline */ | |
if (parse_cmdline(cmdline, &nb_args, args) < 0) { | |
return; | |
} | |
/* if the line ends with a space, it means we want to complete the | |
next arg */ | |
len = strlen(cmdline); | |
if (len > 0 && qemu_isspace(cmdline[len - 1])) { | |
if (nb_args >= MAX_ARGS) { | |
goto cleanup; | |
} | |
args[nb_args++] = g_strdup(""); | |
} | |
/* 2. auto complete according to args */ | |
monitor_find_completion_by_table(mon, mon->cmd_table, args, nb_args); | |
cleanup: | |
free_cmdline_args(args, nb_args); | |
} | |
static int monitor_can_read(void *opaque) | |
{ | |
Monitor *mon = opaque; | |
return (mon->suspend_cnt == 0) ? 1 : 0; | |
} | |
static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens) | |
{ | |
QObject *req, *rsp = NULL, *id = NULL; | |
QDict *qdict = NULL; | |
Monitor *mon = cur_mon; | |
Error *err = NULL; | |
req = json_parser_parse_err(tokens, NULL, &err); | |
if (!req && !err) { | |
/* json_parser_parse_err() sucks: can fail without setting @err */ | |
error_setg(&err, QERR_JSON_PARSING); | |
} | |
if (err) { | |
goto err_out; | |
} | |
qdict = qobject_to_qdict(req); | |
if (qdict) { | |
id = qdict_get(qdict, "id"); | |
qobject_incref(id); | |
qdict_del(qdict, "id"); | |
} /* else will fail qmp_dispatch() */ | |
if (trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) { | |
QString *req_json = qobject_to_json(req); | |
trace_handle_qmp_command(mon, qstring_get_str(req_json)); | |
QDECREF(req_json); | |
} | |
rsp = qmp_dispatch(cur_mon->qmp.commands, req); | |
if (mon->qmp.commands == &qmp_cap_negotiation_commands) { | |
qdict = qdict_get_qdict(qobject_to_qdict(rsp), "error"); | |
if (qdict | |
&& !g_strcmp0(qdict_get_try_str(qdict, "class"), | |
QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) { | |
/* Provide a more useful error message */ | |
qdict_del(qdict, "desc"); | |
qdict_put_str(qdict, "desc", "Expecting capabilities negotiation" | |
" with 'qmp_capabilities'"); | |
} | |
} | |
err_out: | |
if (err) { | |
qdict = qdict_new(); | |
qdict_put_obj(qdict, "error", qmp_build_error_object(err)); | |
error_free(err); | |
rsp = QOBJECT(qdict); | |
} | |
if (rsp) { | |
if (id) { | |
qdict_put_obj(qobject_to_qdict(rsp), "id", id); | |
id = NULL; | |
} | |
monitor_json_emitter(mon, rsp); | |
} | |
qobject_decref(id); | |
qobject_decref(rsp); | |
qobject_decref(req); | |
} | |
static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size) | |
{ | |
Monitor *old_mon = cur_mon; | |
cur_mon = opaque; | |
json_message_parser_feed(&cur_mon->qmp.parser, (const char *) buf, size); | |
cur_mon = old_mon; | |
} | |
static void monitor_read(void *opaque, const uint8_t *buf, int size) | |
{ | |
Monitor *old_mon = cur_mon; | |
int i; | |
cur_mon = opaque; | |
if (cur_mon->rs) { | |
for (i = 0; i < size; i++) | |
readline_handle_byte(cur_mon->rs, buf[i]); | |
} else { | |
if (size == 0 || buf[size - 1] != 0) | |
monitor_printf(cur_mon, "corrupted command\n"); | |
else | |
handle_hmp_command(cur_mon, (char *)buf); | |
} | |
cur_mon = old_mon; | |
} | |
static void monitor_command_cb(void *opaque, const char *cmdline, | |
void *readline_opaque) | |
{ | |
Monitor *mon = opaque; | |
monitor_suspend(mon); | |
handle_hmp_command(mon, cmdline); | |
monitor_resume(mon); | |
} | |
int monitor_suspend(Monitor *mon) | |
{ | |
if (!mon->rs) | |
return -ENOTTY; | |
mon->suspend_cnt++; | |
return 0; | |
} | |
void monitor_resume(Monitor *mon) | |
{ | |
if (!mon->rs) | |
return; | |
if (--mon->suspend_cnt == 0) | |
readline_show_prompt(mon->rs); | |
} | |
static QObject *get_qmp_greeting(void) | |
{ | |
QObject *ver = NULL; | |
qmp_marshal_query_version(NULL, &ver, NULL); | |
return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': []}}", | |
ver); | |
} | |
static void monitor_qmp_event(void *opaque, int event) | |
{ | |
QObject *data; | |
Monitor *mon = opaque; | |
switch (event) { | |
case CHR_EVENT_OPENED: | |
mon->qmp.commands = &qmp_cap_negotiation_commands; | |
data = get_qmp_greeting(); | |
monitor_json_emitter(mon, data); | |
qobject_decref(data); | |
mon_refcount++; | |
break; | |
case CHR_EVENT_CLOSED: | |
json_message_parser_destroy(&mon->qmp.parser); | |
json_message_parser_init(&mon->qmp.parser, handle_qmp_command); | |
mon_refcount--; | |
monitor_fdsets_cleanup(); | |
break; | |
} | |
} | |
static void monitor_event(void *opaque, int event) | |
{ | |
Monitor *mon = opaque; | |
switch (event) { | |
case CHR_EVENT_MUX_IN: | |
qemu_mutex_lock(&mon->out_lock); | |
mon->mux_out = 0; | |
qemu_mutex_unlock(&mon->out_lock); | |
if (mon->reset_seen) { | |
readline_restart(mon->rs); | |
monitor_resume(mon); | |
monitor_flush(mon); | |
} else { | |
mon->suspend_cnt = 0; | |
} | |
break; | |
case CHR_EVENT_MUX_OUT: | |
if (mon->reset_seen) { | |
if (mon->suspend_cnt == 0) { | |
monitor_printf(mon, "\n"); | |
} | |
monitor_flush(mon); | |
monitor_suspend(mon); | |
} else { | |
mon->suspend_cnt++; | |
} | |
qemu_mutex_lock(&mon->out_lock); | |
mon->mux_out = 1; | |
qemu_mutex_unlock(&mon->out_lock); | |
break; | |
case CHR_EVENT_OPENED: | |
monitor_printf(mon, "QEMU %s monitor - type 'help' for more " | |
"information\n", QEMU_VERSION); | |
if (!mon->mux_out) { | |
readline_restart(mon->rs); | |
readline_show_prompt(mon->rs); | |
} | |
mon->reset_seen = 1; | |
mon_refcount++; | |
break; | |
case CHR_EVENT_CLOSED: | |
mon_refcount--; | |
monitor_fdsets_cleanup(); | |
break; | |
} | |
} | |
static int | |
compare_mon_cmd(const void *a, const void *b) | |
{ | |
return strcmp(((const mon_cmd_t *)a)->name, | |
((const mon_cmd_t *)b)->name); | |
} | |
static void sortcmdlist(void) | |
{ | |
int array_num; | |
int elem_size = sizeof(mon_cmd_t); | |
array_num = sizeof(mon_cmds)/elem_size-1; | |
qsort((void *)mon_cmds, array_num, elem_size, compare_mon_cmd); | |
array_num = sizeof(info_cmds)/elem_size-1; | |
qsort((void *)info_cmds, array_num, elem_size, compare_mon_cmd); | |
} | |
/* These functions just adapt the readline interface in a typesafe way. We | |
* could cast function pointers but that discards compiler checks. | |
*/ | |
static void GCC_FMT_ATTR(2, 3) monitor_readline_printf(void *opaque, | |
const char *fmt, ...) | |
{ | |
va_list ap; | |
va_start(ap, fmt); | |
monitor_vprintf(opaque, fmt, ap); | |
va_end(ap); | |
} | |
static void monitor_readline_flush(void *opaque) | |
{ | |
monitor_flush(opaque); | |
} | |
/* | |
* Print to current monitor if we have one, else to stderr. | |
* TODO should return int, so callers can calculate width, but that | |
* requires surgery to monitor_vprintf(). Left for another day. | |
*/ | |
void error_vprintf(const char *fmt, va_list ap) | |
{ | |
if (cur_mon && !monitor_cur_is_qmp()) { | |
monitor_vprintf(cur_mon, fmt, ap); | |
} else { | |
vfprintf(stderr, fmt, ap); | |
} | |
} | |
void error_vprintf_unless_qmp(const char *fmt, va_list ap) | |
{ | |
if (cur_mon && !monitor_cur_is_qmp()) { | |
monitor_vprintf(cur_mon, fmt, ap); | |
} else if (!cur_mon) { | |
vfprintf(stderr, fmt, ap); | |
} | |
} | |
static void __attribute__((constructor)) monitor_lock_init(void) | |
{ | |
qemu_mutex_init(&monitor_lock); | |
} | |
void monitor_init(Chardev *chr, int flags) | |
{ | |
static int is_first_init = 1; | |
Monitor *mon; | |
if (is_first_init) { | |
monitor_qapi_event_init(); | |
sortcmdlist(); | |
is_first_init = 0; | |
} | |
mon = g_malloc(sizeof(*mon)); | |
monitor_data_init(mon); | |
qemu_chr_fe_init(&mon->chr, chr, &error_abort); | |
mon->flags = flags; | |
if (flags & MONITOR_USE_READLINE) { | |
mon->rs = readline_init(monitor_readline_printf, | |
monitor_readline_flush, | |
mon, | |
monitor_find_completion); | |
monitor_read_command(mon, 0); | |
} | |
if (monitor_is_qmp(mon)) { | |
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_qmp_read, | |
monitor_qmp_event, NULL, mon, NULL, true); | |
qemu_chr_fe_set_echo(&mon->chr, true); | |
json_message_parser_init(&mon->qmp.parser, handle_qmp_command); | |
} else { | |
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_read, | |
monitor_event, NULL, mon, NULL, true); | |
} | |
qemu_mutex_lock(&monitor_lock); | |
QLIST_INSERT_HEAD(&mon_list, mon, entry); | |
qemu_mutex_unlock(&monitor_lock); | |
} | |
void monitor_cleanup(void) | |
{ | |
Monitor *mon, *next; | |
qemu_mutex_lock(&monitor_lock); | |
QLIST_FOREACH_SAFE(mon, &mon_list, entry, next) { | |
QLIST_REMOVE(mon, entry); | |
monitor_data_destroy(mon); | |
g_free(mon); | |
} | |
qemu_mutex_unlock(&monitor_lock); | |
} | |
QemuOptsList qemu_mon_opts = { | |
.name = "mon", | |
.implied_opt_name = "chardev", | |
.head = QTAILQ_HEAD_INITIALIZER(qemu_mon_opts.head), | |
.desc = { | |
{ | |
.name = "mode", | |
.type = QEMU_OPT_STRING, | |
},{ | |
.name = "chardev", | |
.type = QEMU_OPT_STRING, | |
},{ | |
.name = "default", /* deprecated */ | |
.type = QEMU_OPT_BOOL, | |
},{ | |
.name = "pretty", | |
.type = QEMU_OPT_BOOL, | |
}, | |
{ /* end of list */ } | |
}, | |
}; | |
#ifndef TARGET_I386 | |
void qmp_rtc_reset_reinjection(Error **errp) | |
{ | |
error_setg(errp, QERR_FEATURE_DISABLED, "rtc-reset-reinjection"); | |
} | |
#endif | |
#ifndef TARGET_S390X | |
void qmp_dump_skeys(const char *filename, Error **errp) | |
{ | |
error_setg(errp, QERR_FEATURE_DISABLED, "dump-skeys"); | |
} | |
#endif | |
#ifndef TARGET_ARM | |
GICCapabilityList *qmp_query_gic_capabilities(Error **errp) | |
{ | |
error_setg(errp, QERR_FEATURE_DISABLED, "query-gic-capabilities"); | |
return NULL; | |
} | |
#endif | |
HotpluggableCPUList *qmp_query_hotpluggable_cpus(Error **errp) | |
{ | |
MachineState *ms = MACHINE(qdev_get_machine()); | |
MachineClass *mc = MACHINE_GET_CLASS(ms); | |
if (!mc->has_hotpluggable_cpus) { | |
error_setg(errp, QERR_FEATURE_DISABLED, "query-hotpluggable-cpus"); | |
return NULL; | |
} | |
return machine_query_hotpluggable_cpus(ms); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment