]>
Commit | Line | Data |
---|---|---|
5c5d69b0 AB |
1 | /* |
2 | * QEMU Plugin API | |
3 | * | |
4 | * This provides the API that is available to the plugins to interact | |
5 | * with QEMU. We have to be careful not to expose internal details of | |
6 | * how QEMU works so we abstract out things like translation and | |
7 | * instructions to anonymous data types: | |
8 | * | |
9 | * qemu_plugin_tb | |
10 | * qemu_plugin_insn | |
8df5e27c | 11 | * qemu_plugin_register |
5c5d69b0 AB |
12 | * |
13 | * Which can then be passed back into the API to do additional things. | |
14 | * As such all the public functions in here are exported in | |
15 | * qemu-plugin.h. | |
16 | * | |
17 | * The general life-cycle of a plugin is: | |
18 | * | |
19 | * - plugin is loaded, public qemu_plugin_install called | |
20 | * - the install func registers callbacks for events | |
21 | * - usually an atexit_cb is registered to dump info at the end | |
22 | * - when a registered event occurs the plugin is called | |
23 | * - some events pass additional info | |
24 | * - during translation the plugin can decide to instrument any | |
25 | * instruction | |
26 | * - when QEMU exits all the registered atexit callbacks are called | |
27 | * | |
28 | * Copyright (C) 2017, Emilio G. Cota <cota@braap.org> | |
29 | * Copyright (C) 2019, Linaro | |
30 | * | |
31 | * License: GNU GPL, version 2 or later. | |
32 | * See the COPYING file in the top-level directory. | |
33 | * | |
34 | * SPDX-License-Identifier: GPL-2.0-or-later | |
35 | * | |
36 | */ | |
37 | ||
38 | #include "qemu/osdep.h" | |
8df5e27c | 39 | #include "qemu/main-loop.h" |
5c5d69b0 | 40 | #include "qemu/plugin.h" |
cd617484 | 41 | #include "qemu/log.h" |
5c5d69b0 | 42 | #include "tcg/tcg.h" |
cbafa236 | 43 | #include "exec/exec-all.h" |
8df5e27c | 44 | #include "exec/gdbstub.h" |
36bc99bc | 45 | #include "exec/translator.h" |
cbafa236 | 46 | #include "disas/disas.h" |
5c5d69b0 AB |
47 | #include "plugin.h" |
48 | #ifndef CONFIG_USER_ONLY | |
155fb465 | 49 | #include "exec/ram_addr.h" |
235537fa | 50 | #include "qemu/plugin-memory.h" |
5c5d69b0 | 51 | #include "hw/boards.h" |
91d40327 IA |
52 | #else |
53 | #include "qemu.h" | |
54 | #ifdef CONFIG_LINUX | |
55 | #include "loader.h" | |
56 | #endif | |
5c5d69b0 AB |
57 | #endif |
58 | ||
59 | /* Uninstall and Reset handlers */ | |
60 | ||
61 | void qemu_plugin_uninstall(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb) | |
62 | { | |
63 | plugin_reset_uninstall(id, cb, false); | |
64 | } | |
65 | ||
66 | void qemu_plugin_reset(qemu_plugin_id_t id, qemu_plugin_simple_cb_t cb) | |
67 | { | |
68 | plugin_reset_uninstall(id, cb, true); | |
69 | } | |
70 | ||
71 | /* | |
72 | * Plugin Register Functions | |
73 | * | |
74 | * This allows the plugin to register callbacks for various events | |
75 | * during the translation. | |
76 | */ | |
77 | ||
78 | void qemu_plugin_register_vcpu_init_cb(qemu_plugin_id_t id, | |
79 | qemu_plugin_vcpu_simple_cb_t cb) | |
80 | { | |
81 | plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_INIT, cb); | |
82 | } | |
83 | ||
84 | void qemu_plugin_register_vcpu_exit_cb(qemu_plugin_id_t id, | |
85 | qemu_plugin_vcpu_simple_cb_t cb) | |
86 | { | |
87 | plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_EXIT, cb); | |
88 | } | |
89 | ||
e5013259 RH |
90 | static bool tb_is_mem_only(void) |
91 | { | |
92 | return tb_cflags(tcg_ctx->gen_tb) & CF_MEMI_ONLY; | |
93 | } | |
94 | ||
5c5d69b0 AB |
95 | void qemu_plugin_register_vcpu_tb_exec_cb(struct qemu_plugin_tb *tb, |
96 | qemu_plugin_vcpu_udata_cb_t cb, | |
97 | enum qemu_plugin_cb_flags flags, | |
98 | void *udata) | |
99 | { | |
e5013259 | 100 | if (!tb_is_mem_only()) { |
db409c01 | 101 | plugin_register_dyn_cb__udata(&tb->cbs, cb, flags, udata); |
cfd405ea | 102 | } |
5c5d69b0 AB |
103 | } |
104 | ||
0bcebaba PB |
105 | void qemu_plugin_register_vcpu_tb_exec_inline_per_vcpu( |
106 | struct qemu_plugin_tb *tb, | |
107 | enum qemu_plugin_op op, | |
108 | qemu_plugin_u64 entry, | |
109 | uint64_t imm) | |
110 | { | |
e5013259 | 111 | if (!tb_is_mem_only()) { |
db409c01 | 112 | plugin_register_inline_op_on_entry(&tb->cbs, 0, op, entry, imm); |
0bcebaba PB |
113 | } |
114 | } | |
115 | ||
5c5d69b0 AB |
116 | void qemu_plugin_register_vcpu_insn_exec_cb(struct qemu_plugin_insn *insn, |
117 | qemu_plugin_vcpu_udata_cb_t cb, | |
118 | enum qemu_plugin_cb_flags flags, | |
119 | void *udata) | |
120 | { | |
e5013259 | 121 | if (!tb_is_mem_only()) { |
db409c01 | 122 | plugin_register_dyn_cb__udata(&insn->insn_cbs, cb, flags, udata); |
cfd405ea | 123 | } |
5c5d69b0 AB |
124 | } |
125 | ||
0bcebaba PB |
126 | void qemu_plugin_register_vcpu_insn_exec_inline_per_vcpu( |
127 | struct qemu_plugin_insn *insn, | |
128 | enum qemu_plugin_op op, | |
129 | qemu_plugin_u64 entry, | |
130 | uint64_t imm) | |
131 | { | |
e5013259 | 132 | if (!tb_is_mem_only()) { |
db409c01 | 133 | plugin_register_inline_op_on_entry(&insn->insn_cbs, 0, op, entry, imm); |
0bcebaba PB |
134 | } |
135 | } | |
136 | ||
5c5d69b0 | 137 | |
cfd405ea AB |
138 | /* |
139 | * We always plant memory instrumentation because they don't finalise until | |
140 | * after the operation has complete. | |
141 | */ | |
5c5d69b0 AB |
142 | void qemu_plugin_register_vcpu_mem_cb(struct qemu_plugin_insn *insn, |
143 | qemu_plugin_vcpu_mem_cb_t cb, | |
144 | enum qemu_plugin_cb_flags flags, | |
145 | enum qemu_plugin_mem_rw rw, | |
146 | void *udata) | |
147 | { | |
db409c01 | 148 | plugin_register_vcpu_mem_cb(&insn->mem_cbs, cb, flags, rw, udata); |
5c5d69b0 AB |
149 | } |
150 | ||
0bcebaba PB |
151 | void qemu_plugin_register_vcpu_mem_inline_per_vcpu( |
152 | struct qemu_plugin_insn *insn, | |
153 | enum qemu_plugin_mem_rw rw, | |
154 | enum qemu_plugin_op op, | |
155 | qemu_plugin_u64 entry, | |
156 | uint64_t imm) | |
157 | { | |
db409c01 | 158 | plugin_register_inline_op_on_entry(&insn->mem_cbs, rw, op, entry, imm); |
0bcebaba PB |
159 | } |
160 | ||
5c5d69b0 AB |
161 | void qemu_plugin_register_vcpu_tb_trans_cb(qemu_plugin_id_t id, |
162 | qemu_plugin_vcpu_tb_trans_cb_t cb) | |
163 | { | |
164 | plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_TB_TRANS, cb); | |
165 | } | |
166 | ||
167 | void qemu_plugin_register_vcpu_syscall_cb(qemu_plugin_id_t id, | |
168 | qemu_plugin_vcpu_syscall_cb_t cb) | |
169 | { | |
170 | plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL, cb); | |
171 | } | |
172 | ||
173 | void | |
174 | qemu_plugin_register_vcpu_syscall_ret_cb(qemu_plugin_id_t id, | |
175 | qemu_plugin_vcpu_syscall_ret_cb_t cb) | |
176 | { | |
177 | plugin_register_cb(id, QEMU_PLUGIN_EV_VCPU_SYSCALL_RET, cb); | |
178 | } | |
179 | ||
180 | /* | |
181 | * Plugin Queries | |
182 | * | |
183 | * These are queries that the plugin can make to gauge information | |
184 | * from our opaque data types. We do not want to leak internal details | |
185 | * here just information useful to the plugin. | |
186 | */ | |
187 | ||
188 | /* | |
189 | * Translation block information: | |
190 | * | |
191 | * A plugin can query the virtual address of the start of the block | |
192 | * and the number of instructions in it. It can also get access to | |
193 | * each translated instruction. | |
194 | */ | |
195 | ||
196 | size_t qemu_plugin_tb_n_insns(const struct qemu_plugin_tb *tb) | |
197 | { | |
198 | return tb->n; | |
199 | } | |
200 | ||
201 | uint64_t qemu_plugin_tb_vaddr(const struct qemu_plugin_tb *tb) | |
202 | { | |
203 | return tb->vaddr; | |
204 | } | |
205 | ||
206 | struct qemu_plugin_insn * | |
207 | qemu_plugin_tb_get_insn(const struct qemu_plugin_tb *tb, size_t idx) | |
208 | { | |
cfd405ea | 209 | struct qemu_plugin_insn *insn; |
5c5d69b0 AB |
210 | if (unlikely(idx >= tb->n)) { |
211 | return NULL; | |
212 | } | |
cfd405ea | 213 | insn = g_ptr_array_index(tb->insns, idx); |
cfd405ea | 214 | return insn; |
5c5d69b0 AB |
215 | } |
216 | ||
217 | /* | |
218 | * Instruction information | |
219 | * | |
220 | * These queries allow the plugin to retrieve information about each | |
221 | * instruction being translated. | |
222 | */ | |
223 | ||
4abc8923 RH |
224 | size_t qemu_plugin_insn_data(const struct qemu_plugin_insn *insn, |
225 | void *dest, size_t len) | |
5c5d69b0 | 226 | { |
36bc99bc RH |
227 | const DisasContextBase *db = tcg_ctx->plugin_db; |
228 | ||
229 | len = MIN(len, insn->len); | |
230 | return translator_st(db, dest, insn->vaddr, len) ? len : 0; | |
5c5d69b0 AB |
231 | } |
232 | ||
233 | size_t qemu_plugin_insn_size(const struct qemu_plugin_insn *insn) | |
234 | { | |
36bc99bc | 235 | return insn->len; |
5c5d69b0 AB |
236 | } |
237 | ||
238 | uint64_t qemu_plugin_insn_vaddr(const struct qemu_plugin_insn *insn) | |
239 | { | |
240 | return insn->vaddr; | |
241 | } | |
242 | ||
243 | void *qemu_plugin_insn_haddr(const struct qemu_plugin_insn *insn) | |
244 | { | |
d3ace105 RH |
245 | const DisasContextBase *db = tcg_ctx->plugin_db; |
246 | vaddr page0_last = db->pc_first | ~TARGET_PAGE_MASK; | |
247 | ||
248 | if (db->fake_insn) { | |
249 | return NULL; | |
250 | } | |
251 | ||
252 | /* | |
253 | * ??? The return value is not intended for use of host memory, | |
254 | * but as a proxy for address space and physical address. | |
255 | * Thus we are only interested in the first byte and do not | |
256 | * care about spanning pages. | |
257 | */ | |
258 | if (insn->vaddr <= page0_last) { | |
259 | if (db->host_addr[0] == NULL) { | |
260 | return NULL; | |
261 | } | |
262 | return db->host_addr[0] + insn->vaddr - db->pc_first; | |
263 | } else { | |
264 | if (db->host_addr[1] == NULL) { | |
265 | return NULL; | |
266 | } | |
267 | return db->host_addr[1] + insn->vaddr - (page0_last + 1); | |
268 | } | |
5c5d69b0 AB |
269 | } |
270 | ||
cbafa236 AB |
271 | char *qemu_plugin_insn_disas(const struct qemu_plugin_insn *insn) |
272 | { | |
273 | CPUState *cpu = current_cpu; | |
36bc99bc | 274 | return plugin_disas(cpu, insn->vaddr, insn->len); |
cbafa236 AB |
275 | } |
276 | ||
7c4ab60f AB |
277 | const char *qemu_plugin_insn_symbol(const struct qemu_plugin_insn *insn) |
278 | { | |
279 | const char *sym = lookup_symbol(insn->vaddr); | |
280 | return sym[0] != 0 ? sym : NULL; | |
281 | } | |
282 | ||
5c5d69b0 AB |
283 | /* |
284 | * The memory queries allow the plugin to query information about a | |
285 | * memory access. | |
286 | */ | |
287 | ||
288 | unsigned qemu_plugin_mem_size_shift(qemu_plugin_meminfo_t info) | |
289 | { | |
37aff087 RH |
290 | MemOp op = get_memop(info); |
291 | return op & MO_SIZE; | |
5c5d69b0 AB |
292 | } |
293 | ||
294 | bool qemu_plugin_mem_is_sign_extended(qemu_plugin_meminfo_t info) | |
295 | { | |
37aff087 RH |
296 | MemOp op = get_memop(info); |
297 | return op & MO_SIGN; | |
5c5d69b0 AB |
298 | } |
299 | ||
300 | bool qemu_plugin_mem_is_big_endian(qemu_plugin_meminfo_t info) | |
301 | { | |
37aff087 RH |
302 | MemOp op = get_memop(info); |
303 | return (op & MO_BSWAP) == MO_BE; | |
5c5d69b0 AB |
304 | } |
305 | ||
306 | bool qemu_plugin_mem_is_store(qemu_plugin_meminfo_t info) | |
307 | { | |
37aff087 | 308 | return get_plugin_meminfo_rw(info) & QEMU_PLUGIN_MEM_W; |
5c5d69b0 AB |
309 | } |
310 | ||
311 | /* | |
312 | * Virtual Memory queries | |
313 | */ | |
314 | ||
235537fa AB |
315 | #ifdef CONFIG_SOFTMMU |
316 | static __thread struct qemu_plugin_hwaddr hwaddr_info; | |
a2b88169 | 317 | #endif |
235537fa AB |
318 | |
319 | struct qemu_plugin_hwaddr *qemu_plugin_get_hwaddr(qemu_plugin_meminfo_t info, | |
320 | uint64_t vaddr) | |
321 | { | |
a2b88169 | 322 | #ifdef CONFIG_SOFTMMU |
235537fa | 323 | CPUState *cpu = current_cpu; |
37aff087 RH |
324 | unsigned int mmu_idx = get_mmuidx(info); |
325 | enum qemu_plugin_mem_rw rw = get_plugin_meminfo_rw(info); | |
326 | hwaddr_info.is_store = (rw & QEMU_PLUGIN_MEM_W) != 0; | |
235537fa | 327 | |
5413c37f RH |
328 | assert(mmu_idx < NB_MMU_MODES); |
329 | ||
235537fa | 330 | if (!tlb_plugin_lookup(cpu, vaddr, mmu_idx, |
37aff087 | 331 | hwaddr_info.is_store, &hwaddr_info)) { |
235537fa AB |
332 | error_report("invalid use of qemu_plugin_get_hwaddr"); |
333 | return NULL; | |
334 | } | |
335 | ||
336 | return &hwaddr_info; | |
235537fa | 337 | #else |
5c5d69b0 | 338 | return NULL; |
235537fa | 339 | #endif |
a2b88169 | 340 | } |
235537fa | 341 | |
308e7549 | 342 | bool qemu_plugin_hwaddr_is_io(const struct qemu_plugin_hwaddr *haddr) |
235537fa AB |
343 | { |
344 | #ifdef CONFIG_SOFTMMU | |
308e7549 | 345 | return haddr->is_io; |
235537fa AB |
346 | #else |
347 | return false; | |
348 | #endif | |
349 | } | |
350 | ||
787148bf | 351 | uint64_t qemu_plugin_hwaddr_phys_addr(const struct qemu_plugin_hwaddr *haddr) |
235537fa AB |
352 | { |
353 | #ifdef CONFIG_SOFTMMU | |
354 | if (haddr) { | |
405c02d8 | 355 | return haddr->phys_addr; |
235537fa AB |
356 | } |
357 | #endif | |
358 | return 0; | |
359 | } | |
5c5d69b0 | 360 | |
b853a79f AB |
361 | const char *qemu_plugin_hwaddr_device_name(const struct qemu_plugin_hwaddr *h) |
362 | { | |
363 | #ifdef CONFIG_SOFTMMU | |
364 | if (h && h->is_io) { | |
405c02d8 RH |
365 | MemoryRegion *mr = h->mr; |
366 | if (!mr->name) { | |
367 | unsigned maddr = (uintptr_t)mr; | |
368 | g_autofree char *temp = g_strdup_printf("anon%08x", maddr); | |
b853a79f AB |
369 | return g_intern_string(temp); |
370 | } else { | |
405c02d8 | 371 | return g_intern_string(mr->name); |
b853a79f AB |
372 | } |
373 | } else { | |
374 | return g_intern_static_string("RAM"); | |
375 | } | |
376 | #else | |
377 | return g_intern_static_string("Invalid"); | |
378 | #endif | |
379 | } | |
380 | ||
4a448b14 PB |
381 | int qemu_plugin_num_vcpus(void) |
382 | { | |
383 | return plugin_num_vcpus(); | |
384 | } | |
385 | ||
ca76a669 AB |
386 | /* |
387 | * Plugin output | |
388 | */ | |
389 | void qemu_plugin_outs(const char *string) | |
390 | { | |
391 | qemu_log_mask(CPU_LOG_PLUGIN, "%s", string); | |
392 | } | |
6a9e8a08 MM |
393 | |
394 | bool qemu_plugin_bool_parse(const char *name, const char *value, bool *ret) | |
395 | { | |
396 | return name && value && qapi_bool_parse(name, value, ret, NULL); | |
397 | } | |
91d40327 IA |
398 | |
399 | /* | |
400 | * Binary path, start and end locations | |
401 | */ | |
402 | const char *qemu_plugin_path_to_binary(void) | |
403 | { | |
404 | char *path = NULL; | |
405 | #ifdef CONFIG_USER_ONLY | |
e4e5cb4a | 406 | TaskState *ts = get_task_state(current_cpu); |
91d40327 IA |
407 | path = g_strdup(ts->bprm->filename); |
408 | #endif | |
409 | return path; | |
410 | } | |
411 | ||
412 | uint64_t qemu_plugin_start_code(void) | |
413 | { | |
414 | uint64_t start = 0; | |
415 | #ifdef CONFIG_USER_ONLY | |
e4e5cb4a | 416 | TaskState *ts = get_task_state(current_cpu); |
91d40327 IA |
417 | start = ts->info->start_code; |
418 | #endif | |
419 | return start; | |
420 | } | |
421 | ||
422 | uint64_t qemu_plugin_end_code(void) | |
423 | { | |
424 | uint64_t end = 0; | |
425 | #ifdef CONFIG_USER_ONLY | |
e4e5cb4a | 426 | TaskState *ts = get_task_state(current_cpu); |
91d40327 IA |
427 | end = ts->info->end_code; |
428 | #endif | |
429 | return end; | |
430 | } | |
431 | ||
432 | uint64_t qemu_plugin_entry_code(void) | |
433 | { | |
434 | uint64_t entry = 0; | |
435 | #ifdef CONFIG_USER_ONLY | |
e4e5cb4a | 436 | TaskState *ts = get_task_state(current_cpu); |
91d40327 IA |
437 | entry = ts->info->entry; |
438 | #endif | |
439 | return entry; | |
440 | } | |
8df5e27c AB |
441 | |
442 | /* | |
443 | * Create register handles. | |
444 | * | |
445 | * We need to create a handle for each register so the plugin | |
446 | * infrastructure can call gdbstub to read a register. They are | |
447 | * currently just a pointer encapsulation of the gdb_reg but in | |
448 | * future may hold internal plugin state so its important plugin | |
449 | * authors are not tempted to treat them as numbers. | |
450 | * | |
451 | * We also construct a result array with those handles and some | |
452 | * ancillary data the plugin might find useful. | |
453 | */ | |
454 | ||
455 | static GArray *create_register_handles(GArray *gdbstub_regs) | |
456 | { | |
457 | GArray *find_data = g_array_new(true, true, | |
458 | sizeof(qemu_plugin_reg_descriptor)); | |
459 | ||
460 | for (int i = 0; i < gdbstub_regs->len; i++) { | |
461 | GDBRegDesc *grd = &g_array_index(gdbstub_regs, GDBRegDesc, i); | |
462 | qemu_plugin_reg_descriptor desc; | |
463 | ||
464 | /* skip "un-named" regs */ | |
465 | if (!grd->name) { | |
466 | continue; | |
467 | } | |
468 | ||
469 | /* Create a record for the plugin */ | |
470 | desc.handle = GINT_TO_POINTER(grd->gdb_reg); | |
471 | desc.name = g_intern_string(grd->name); | |
472 | desc.feature = g_intern_string(grd->feature_name); | |
473 | g_array_append_val(find_data, desc); | |
474 | } | |
475 | ||
476 | return find_data; | |
477 | } | |
478 | ||
479 | GArray *qemu_plugin_get_registers(void) | |
480 | { | |
481 | g_assert(current_cpu); | |
482 | ||
483 | g_autoptr(GArray) regs = gdb_get_register_list(current_cpu); | |
484 | return create_register_handles(regs); | |
485 | } | |
486 | ||
487 | int qemu_plugin_read_register(struct qemu_plugin_register *reg, GByteArray *buf) | |
488 | { | |
489 | g_assert(current_cpu); | |
490 | ||
491 | return gdb_read_register(current_cpu, buf, GPOINTER_TO_INT(reg)); | |
492 | } | |
a3c2cf0b PB |
493 | |
494 | struct qemu_plugin_scoreboard *qemu_plugin_scoreboard_new(size_t element_size) | |
495 | { | |
496 | return plugin_scoreboard_new(element_size); | |
497 | } | |
498 | ||
499 | void qemu_plugin_scoreboard_free(struct qemu_plugin_scoreboard *score) | |
500 | { | |
501 | plugin_scoreboard_free(score); | |
502 | } | |
503 | ||
504 | void *qemu_plugin_scoreboard_find(struct qemu_plugin_scoreboard *score, | |
505 | unsigned int vcpu_index) | |
506 | { | |
507 | g_assert(vcpu_index < qemu_plugin_num_vcpus()); | |
508 | /* we can't use g_array_index since entry size is not statically known */ | |
509 | char *base_ptr = score->data->data; | |
510 | return base_ptr + vcpu_index * g_array_get_element_size(score->data); | |
511 | } | |
8042e2ea PB |
512 | |
513 | static uint64_t *plugin_u64_address(qemu_plugin_u64 entry, | |
514 | unsigned int vcpu_index) | |
515 | { | |
516 | char *ptr = qemu_plugin_scoreboard_find(entry.score, vcpu_index); | |
517 | return (uint64_t *)(ptr + entry.offset); | |
518 | } | |
519 | ||
520 | void qemu_plugin_u64_add(qemu_plugin_u64 entry, unsigned int vcpu_index, | |
521 | uint64_t added) | |
522 | { | |
523 | *plugin_u64_address(entry, vcpu_index) += added; | |
524 | } | |
525 | ||
526 | uint64_t qemu_plugin_u64_get(qemu_plugin_u64 entry, | |
527 | unsigned int vcpu_index) | |
528 | { | |
529 | return *plugin_u64_address(entry, vcpu_index); | |
530 | } | |
531 | ||
532 | void qemu_plugin_u64_set(qemu_plugin_u64 entry, unsigned int vcpu_index, | |
533 | uint64_t val) | |
534 | { | |
535 | *plugin_u64_address(entry, vcpu_index) = val; | |
536 | } | |
537 | ||
538 | uint64_t qemu_plugin_u64_sum(qemu_plugin_u64 entry) | |
539 | { | |
540 | uint64_t total = 0; | |
541 | for (int i = 0, n = qemu_plugin_num_vcpus(); i < n; ++i) { | |
542 | total += qemu_plugin_u64_get(entry, i); | |
543 | } | |
544 | return total; | |
545 | } |