| @@ -0,0 +1,273 @@ |
| 1 | +#define _GNU_SOURCE |
| 2 | +#include <caml/mlvalues.h> |
| 3 | +#include <caml/memory.h> |
| 4 | +#include <caml/alloc.h> |
| 5 | +#include <caml/custom.h> |
| 6 | +#include <caml/fail.h> |
| 7 | +#include <stdbool.h> |
| 8 | +#include <stdint.h> |
| 9 | +#include <string.h> |
| 10 | +#include <stdio.h> |
| 11 | + |
| 12 | +#if defined(__has_include) |
| 13 | +#if __has_include(<xenctrl.h>) && __has_include(<xenforeignmemory.h>) |
| 14 | +#define SCOPE_XEN 1 |
| 15 | +#endif |
| 16 | +#endif |
| 17 | + |
| 18 | +#ifdef SCOPE_XEN |
| 19 | +#include <dlfcn.h> |
| 20 | +#include <errno.h> |
| 21 | +#include <sys/mman.h> |
| 22 | +#include <xenctrl.h> |
| 23 | +#include <xenforeignmemory.h> |
| 24 | +#include <xentoollog.h> |
| 25 | +#include <xen/hvm/save.h> |
| 26 | + |
| 27 | +static void quiet_message(xentoollog_logger *logger, xentoollog_level level, |
| 28 | + int error, const char *context, const char *format, va_list arguments) { |
| 29 | + (void)logger; (void)level; (void)error; (void)context; (void)format; (void)arguments; |
| 30 | +} |
| 31 | + |
| 32 | +static void quiet_destroy(xentoollog_logger *logger) { |
| 33 | + (void)logger; |
| 34 | +} |
| 35 | + |
| 36 | +static xentoollog_logger quiet_logger = { |
| 37 | + .vmessage = quiet_message, |
| 38 | + .progress = NULL, |
| 39 | + .destroy = quiet_destroy |
| 40 | +}; |
| 41 | + |
| 42 | +struct api { |
| 43 | + void *control; |
| 44 | + void *foreign; |
| 45 | + __typeof__(&xc_interface_open) control_open; |
| 46 | + __typeof__(&xc_interface_close) control_close; |
| 47 | + __typeof__(&xc_domain_getinfolist) info; |
| 48 | + __typeof__(&xc_domain_pause) pause; |
| 49 | + __typeof__(&xc_domain_unpause) unpause; |
| 50 | + __typeof__(&xc_domain_hvm_getcontext_partial) context; |
| 51 | + __typeof__(&xenforeignmemory_open) foreign_open; |
| 52 | + __typeof__(&xenforeignmemory_close) foreign_close; |
| 53 | + __typeof__(&xenforeignmemory_map) map; |
| 54 | + __typeof__(&xenforeignmemory_unmap) unmap; |
| 55 | +}; |
| 56 | + |
| 57 | +static struct api api; |
| 58 | + |
| 59 | +static bool load_api(void) { |
| 60 | + if (api.control && api.foreign) return true; |
| 61 | + api.control = dlopen("libxenctrl.so", RTLD_NOW | RTLD_LOCAL); |
| 62 | + api.foreign = dlopen("libxenforeignmemory.so", RTLD_NOW | RTLD_LOCAL); |
| 63 | + if (!api.control || !api.foreign) goto failed; |
| 64 | +#define LOAD(member, library, symbol) \ |
| 65 | + do { *(void **)(&api.member) = dlsym(api.library, symbol); if (!api.member) goto failed; } while (0) |
| 66 | + LOAD(control_open, control, "xc_interface_open"); |
| 67 | + LOAD(control_close, control, "xc_interface_close"); |
| 68 | + LOAD(info, control, "xc_domain_getinfolist"); |
| 69 | + LOAD(pause, control, "xc_domain_pause"); |
| 70 | + LOAD(unpause, control, "xc_domain_unpause"); |
| 71 | + LOAD(context, control, "xc_domain_hvm_getcontext_partial"); |
| 72 | + LOAD(foreign_open, foreign, "xenforeignmemory_open"); |
| 73 | + LOAD(foreign_close, foreign, "xenforeignmemory_close"); |
| 74 | + LOAD(map, foreign, "xenforeignmemory_map"); |
| 75 | + LOAD(unmap, foreign, "xenforeignmemory_unmap"); |
| 76 | +#undef LOAD |
| 77 | + return true; |
| 78 | +failed: |
| 79 | + if (api.control) dlclose(api.control); |
| 80 | + if (api.foreign) dlclose(api.foreign); |
| 81 | + memset(&api, 0, sizeof(api)); |
| 82 | + return false; |
| 83 | +} |
| 84 | + |
| 85 | +struct session { |
| 86 | + xc_interface *control; |
| 87 | + xenforeignmemory_handle *foreign; |
| 88 | + uint32_t domid; |
| 89 | + uint16_t vcpu; |
| 90 | + bool owned_pause; |
| 91 | + void *mapping; |
| 92 | + xen_domain_handle_t identity; |
| 93 | +}; |
| 94 | + |
| 95 | +static bool inspect(struct session *s, xc_domaininfo_t *info) { |
| 96 | + return api.info(s->control, s->domid, 1, info) == 1 && info->domain == s->domid && |
| 97 | + memcmp(info->handle, s->identity, sizeof(s->identity)) == 0; |
| 98 | +} |
| 99 | + |
| 100 | +static int release(struct session *s) { |
| 101 | + int failed = 0; |
| 102 | + if (s->mapping) { |
| 103 | + if (api.unmap(s->foreign, s->mapping, 1) != 0) failed = 1; |
| 104 | + s->mapping = NULL; |
| 105 | + } |
| 106 | + if (s->owned_pause) { |
| 107 | + xc_domaininfo_t info; |
| 108 | + if (!inspect(s, &info) || api.unpause(s->control, s->domid) != 0) failed = 1; |
| 109 | + s->owned_pause = false; |
| 110 | + } |
| 111 | + if (s->foreign && api.foreign_close(s->foreign) != 0) failed = 1; |
| 112 | + if (s->control && api.control_close(s->control) != 0) failed = 1; |
| 113 | + s->foreign = NULL; |
| 114 | + s->control = NULL; |
| 115 | + return failed; |
| 116 | +} |
| 117 | + |
| 118 | +static void finalise(value block) { |
| 119 | + struct session *s = Data_custom_val(block); |
| 120 | + if (release(s)) fprintf(stderr, "scope: xen cleanup failed; inspect domain pause state\n"); |
| 121 | +} |
| 122 | + |
| 123 | +static struct custom_operations operations = { |
| 124 | + .identifier = "scope.xen.session.v1", |
| 125 | + .finalize = finalise, |
| 126 | + .compare = custom_compare_default, |
| 127 | + .hash = custom_hash_default, |
| 128 | + .serialize = custom_serialize_default, |
| 129 | + .deserialize = custom_deserialize_default, |
| 130 | + .compare_ext = custom_compare_ext_default, |
| 131 | + .fixed_length = custom_fixed_length_default |
| 132 | +}; |
| 133 | + |
| 134 | +static void check(struct session *s) { |
| 135 | + xc_domaininfo_t info; |
| 136 | + if (!s->control) caml_failwith("xen session is closed"); |
| 137 | + if (!inspect(s, &info)) caml_failwith("domain identity changed or domain disappeared"); |
| 138 | + if (!(info.flags & XEN_DOMINF_paused)) caml_failwith("domain is no longer paused"); |
| 139 | +} |
| 140 | + |
| 141 | +CAMLprim value scope_xen_available(value unit) { |
| 142 | + CAMLparam1(unit); |
| 143 | + CAMLreturn(Val_bool(load_api())); |
| 144 | +} |
| 145 | + |
| 146 | +CAMLprim value scope_xen_open(value domain, value vcpu, value pause) { |
| 147 | + CAMLparam3(domain, vcpu, pause); |
| 148 | + CAMLlocal1(block); |
| 149 | + if (!load_api()) caml_failwith("cannot load xen control and foreign memory libraries"); |
| 150 | + block = caml_alloc_custom(&operations, sizeof(struct session), 0, 1); |
| 151 | + struct session *s = Data_custom_val(block); |
| 152 | + memset(s, 0, sizeof(*s)); |
| 153 | + s->domid = Int_val(domain); |
| 154 | + s->vcpu = Int_val(vcpu); |
| 155 | + s->control = api.control_open(&quiet_logger, &quiet_logger, 0); |
| 156 | + if (!s->control) caml_failwith("cannot open xen control interface"); |
| 157 | + xc_domaininfo_t info; |
| 158 | + if (api.info(s->control, s->domid, 1, &info) != 1 || info.domain != s->domid || |
| 159 | + !(info.flags & XEN_DOMINF_hvm_guest) || s->vcpu > info.max_vcpu_id) { |
| 160 | + release(s); |
| 161 | + caml_failwith("domain absent, not hvm, or vcpu outside domain range"); |
| 162 | + } |
| 163 | + memcpy(s->identity, info.handle, sizeof(s->identity)); |
| 164 | + if (Bool_val(pause)) { |
| 165 | + if (api.pause(s->control, s->domid) != 0) { |
| 166 | + release(s); |
| 167 | + caml_failwith("cannot acquire xen controller pause"); |
| 168 | + } |
| 169 | + /* pause -> capture -> unpause, release only the reference acquired here */ |
| 170 | + s->owned_pause = true; |
| 171 | + } else if (!(info.flags & XEN_DOMINF_paused)) { |
| 172 | + release(s); |
| 173 | + caml_failwith("domain must already be paused or capture must use --pause"); |
| 174 | + } |
| 175 | + s->foreign = api.foreign_open(&quiet_logger, 0); |
| 176 | + if (!s->foreign) { |
| 177 | + if (release(s)) caml_failwith("foreign memory open and pause cleanup failed; inspect domain"); |
| 178 | + caml_failwith("cannot open xen foreign memory interface"); |
| 179 | + } |
| 180 | + CAMLreturn(block); |
| 181 | +} |
| 182 | + |
| 183 | +CAMLprim value scope_xen_registers(value block) { |
| 184 | + CAMLparam1(block); |
| 185 | + CAMLlocal4(array, number, identity, pair); |
| 186 | + struct session *s = Data_custom_val(block); |
| 187 | + check(s); |
| 188 | + struct hvm_hw_cpu cpu; |
| 189 | + if (api.context(s->control, s->domid, HVM_SAVE_CODE(CPU), s->vcpu, &cpu, sizeof(cpu)) != 0) |
| 190 | + caml_failwith("cannot read hvm vcpu context"); |
| 191 | + uint64_t values[] = {cpu.cr0, cpu.cr2, cpu.cr3, cpu.cr4, cpu.msr_efer, cpu.rflags, cpu.rip}; |
| 192 | + char encoded[sizeof(s->identity) * 2 + 1]; |
| 193 | + for (size_t i = 0; i < sizeof(s->identity); i++) snprintf(encoded + i * 2, 3, "%02x", s->identity[i]); |
| 194 | + array = caml_alloc(7, 0); |
| 195 | + for (int i = 0; i < 7; i++) Store_field(array, i, Val_unit); |
| 196 | + for (int i = 0; i < 7; i++) { |
| 197 | + number = caml_copy_int64(values[i]); |
| 198 | + Store_field(array, i, number); |
| 199 | + } |
| 200 | + identity = caml_copy_string(encoded); |
| 201 | + pair = caml_alloc_tuple(2); |
| 202 | + Store_field(pair, 0, array); |
| 203 | + Store_field(pair, 1, identity); |
| 204 | + CAMLreturn(pair); |
| 205 | +} |
| 206 | + |
| 207 | +CAMLprim value scope_xen_read(value block, value physical) { |
| 208 | + CAMLparam2(block, physical); |
| 209 | + CAMLlocal1(bytes); |
| 210 | + struct session *s = Data_custom_val(block); |
| 211 | + check(s); |
| 212 | + if (s->mapping) caml_failwith("previous foreign unmap failed"); |
| 213 | + uint64_t address = Int64_val(physical); |
| 214 | + if (address & 4095) caml_invalid_argument("unaligned guest physical frame"); |
| 215 | + xen_pfn_t frame = address >> 12; |
| 216 | + int error = 0; |
| 217 | + void *mapping = api.map(s->foreign, s->domid, PROT_READ, 1, &frame, &error); |
| 218 | + if (mapping && mapping != MAP_FAILED) s->mapping = mapping; |
| 219 | + if (!mapping || mapping == MAP_FAILED || error) { |
| 220 | + int saved_error = error ? error : errno; |
| 221 | + if (s->mapping && api.unmap(s->foreign, mapping, 1) == 0) s->mapping = NULL; |
| 222 | + char reason[160]; |
| 223 | + snprintf(reason, sizeof(reason), "foreign map failed at 0x%016llx: errno=%d", |
| 224 | + (unsigned long long)address, saved_error); |
| 225 | + caml_failwith(reason); |
| 226 | + } |
| 227 | + char copy[4096]; |
| 228 | + memcpy(copy, mapping, sizeof(copy)); |
| 229 | + if (api.unmap(s->foreign, mapping, 1) != 0) caml_failwith("foreign unmap failed"); |
| 230 | + s->mapping = NULL; |
| 231 | + bytes = caml_alloc_initialized_string(sizeof(copy), copy); |
| 232 | + CAMLreturn(bytes); |
| 233 | +} |
| 234 | + |
| 235 | +CAMLprim value scope_xen_close(value block) { |
| 236 | + CAMLparam1(block); |
| 237 | + if (release(Data_custom_val(block))) caml_failwith("xen cleanup failed; inspect domain pause state"); |
| 238 | + CAMLreturn(Val_unit); |
| 239 | +} |
| 240 | + |
| 241 | +#else |
| 242 | +CAMLprim value scope_xen_available(value unit) { |
| 243 | + (void)unit; |
| 244 | + return Val_false; |
| 245 | +} |
| 246 | + |
| 247 | +static void unavailable(void) { |
| 248 | + caml_failwith("xen capture unavailable; install xen development headers and rebuild scope"); |
| 249 | +} |
| 250 | + |
| 251 | +CAMLprim value scope_xen_open(value domain, value vcpu, value pause) { |
| 252 | + (void)domain; (void)vcpu; (void)pause; |
| 253 | + unavailable(); |
| 254 | + return Val_unit; |
| 255 | +} |
| 256 | + |
| 257 | +CAMLprim value scope_xen_registers(value block) { |
| 258 | + (void)block; |
| 259 | + unavailable(); |
| 260 | + return Val_unit; |
| 261 | +} |
| 262 | + |
| 263 | +CAMLprim value scope_xen_read(value block, value address) { |
| 264 | + (void)block; (void)address; |
| 265 | + unavailable(); |
| 266 | + return Val_unit; |
| 267 | +} |
| 268 | + |
| 269 | +CAMLprim value scope_xen_close(value block) { |
| 270 | + (void)block; |
| 271 | + return Val_unit; |
| 272 | +} |
| 273 | +#endif |