1 | //===-- cli-wrapper-mpxtable.cpp --------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // C++ includes |
10 | #include <cerrno> |
11 | #include <string> |
12 | |
13 | #include "cli-wrapper-mpxtable.h" |
14 | #include "lldb/API/SBCommandInterpreter.h" |
15 | #include "lldb/API/SBCommandReturnObject.h" |
16 | #include "lldb/API/SBMemoryRegionInfo.h" |
17 | #include "lldb/API/SBProcess.h" |
18 | #include "lldb/API/SBTarget.h" |
19 | #include "lldb/API/SBThread.h" |
20 | |
21 | #include "llvm/ADT/Twine.h" |
22 | #include "llvm/TargetParser/Triple.h" |
23 | |
24 | static bool GetPtr(char *cptr, uint64_t &ptr, lldb::SBFrame &frame, |
25 | lldb::SBCommandReturnObject &result) { |
26 | if (!cptr) { |
27 | result.SetError("Bad argument." ); |
28 | result.SetStatus(lldb::eReturnStatusFailed); |
29 | return false; |
30 | } |
31 | |
32 | lldb::SBValue ptr_addr = frame.GetValueForVariablePath(var_path: cptr); |
33 | if (!ptr_addr.IsValid()) { |
34 | result.SetError("Invalid pointer." ); |
35 | result.SetStatus(lldb::eReturnStatusFailed); |
36 | return false; |
37 | } |
38 | ptr = ptr_addr.GetLoadAddress(); |
39 | return true; |
40 | } |
41 | |
42 | enum { |
43 | mpx_base_mask_64 = ~(uint64_t)0xFFFULL, |
44 | mpx_bd_mask_64 = 0xFFFFFFF00000ULL, |
45 | bd_r_shift_64 = 20, |
46 | bd_l_shift_64 = 3, |
47 | bt_r_shift_64 = 3, |
48 | bt_l_shift_64 = 5, |
49 | bt_mask_64 = 0x0000000FFFF8ULL, |
50 | |
51 | mpx_base_mask_32 = 0xFFFFFFFFFFFFF000ULL, |
52 | mpx_bd_mask_32 = 0xFFFFF000ULL, |
53 | bd_r_shift_32 = 12, |
54 | bd_l_shift_32 = 2, |
55 | bt_r_shift_32 = 2, |
56 | bt_l_shift_32 = 4, |
57 | bt_mask_32 = 0x00000FFCULL, |
58 | }; |
59 | |
60 | static void PrintBTEntry(lldb::addr_t lbound, lldb::addr_t ubound, |
61 | uint64_t value, uint64_t meta, |
62 | lldb::SBCommandReturnObject &result) { |
63 | const lldb::addr_t one_cmpl64 = ~((lldb::addr_t)0); |
64 | const lldb::addr_t one_cmpl32 = ~((uint32_t)0); |
65 | |
66 | if ((lbound == one_cmpl64 || lbound == one_cmpl32) && ubound == 0) { |
67 | result.Printf(format: "Null bounds on map: pointer value = 0x%" PRIu64 "\n" , value); |
68 | } else { |
69 | result.Printf(format: " lbound = 0x%" PRIu64 "," , lbound); |
70 | result.Printf(format: " ubound = 0x%" PRIu64 , ubound); |
71 | result.Printf(format: " (pointer value = 0x%" PRIu64 "," , value); |
72 | result.Printf(format: " metadata = 0x%" PRIu64 ")\n" , meta); |
73 | } |
74 | } |
75 | |
76 | static bool GetBTEntryAddr(uint64_t bndcfgu, uint64_t ptr, |
77 | lldb::SBTarget &target, llvm::Triple::ArchType arch, |
78 | size_t &size, lldb::addr_t &bt_entry_addr, |
79 | lldb::SBCommandReturnObject &result, |
80 | lldb::SBError &error) { |
81 | lldb::addr_t mpx_base_mask; |
82 | lldb::addr_t mpx_bd_mask; |
83 | lldb::addr_t bd_r_shift; |
84 | lldb::addr_t bd_l_shift; |
85 | lldb::addr_t bt_r_shift; |
86 | lldb::addr_t bt_l_shift; |
87 | lldb::addr_t bt_mask; |
88 | |
89 | if (arch == llvm::Triple::ArchType::x86_64) { |
90 | mpx_base_mask = mpx_base_mask_64; |
91 | mpx_bd_mask = mpx_bd_mask_64; |
92 | bd_r_shift = bd_r_shift_64; |
93 | bd_l_shift = bd_l_shift_64; |
94 | bt_r_shift = bt_r_shift_64; |
95 | bt_l_shift = bt_l_shift_64; |
96 | bt_mask = bt_mask_64; |
97 | } else if (arch == llvm::Triple::ArchType::x86) { |
98 | mpx_base_mask = mpx_base_mask_32; |
99 | mpx_bd_mask = mpx_bd_mask_32; |
100 | bd_r_shift = bd_r_shift_32; |
101 | bd_l_shift = bd_l_shift_32; |
102 | bt_r_shift = bt_r_shift_32; |
103 | bt_l_shift = bt_l_shift_32; |
104 | bt_mask = bt_mask_32; |
105 | } else { |
106 | result.SetError("Invalid arch." ); |
107 | result.SetStatus(lldb::eReturnStatusFailed); |
108 | return false; |
109 | } |
110 | |
111 | size = target.GetAddressByteSize(); |
112 | lldb::addr_t mpx_bd_base = bndcfgu & mpx_base_mask; |
113 | lldb::addr_t bd_entry_offset = ((ptr & mpx_bd_mask) >> bd_r_shift) |
114 | << bd_l_shift; |
115 | lldb::addr_t bd_entry_addr = mpx_bd_base + bd_entry_offset; |
116 | |
117 | std::vector<uint8_t> bd_entry_v(size); |
118 | size_t ret = target.GetProcess().ReadMemory( |
119 | addr: bd_entry_addr, buf: static_cast<void *>(bd_entry_v.data()), size, error); |
120 | if (ret != size || !error.Success()) { |
121 | result.SetError("Failed access to BD entry." ); |
122 | return false; |
123 | } |
124 | |
125 | lldb::SBData data; |
126 | data.SetData(error, buf: bd_entry_v.data(), size: bd_entry_v.size(), |
127 | endian: target.GetByteOrder(), addr_size: size); |
128 | lldb::addr_t bd_entry = data.GetAddress(error, offset: 0); |
129 | |
130 | if (!error.Success()) { |
131 | result.SetError("Failed access to BD entry." ); |
132 | return false; |
133 | } |
134 | |
135 | if ((bd_entry & 0x01) == 0) { |
136 | result.SetError("Invalid bound directory." ); |
137 | result.SetStatus(lldb::eReturnStatusFailed); |
138 | return false; |
139 | } |
140 | |
141 | // Clear status bit. |
142 | // |
143 | bd_entry--; |
144 | |
145 | lldb::addr_t bt_addr = bd_entry & ~bt_r_shift; |
146 | lldb::addr_t bt_entry_offset = ((ptr & bt_mask) >> bt_r_shift) << bt_l_shift; |
147 | bt_entry_addr = bt_addr + bt_entry_offset; |
148 | |
149 | return true; |
150 | } |
151 | |
152 | static bool GetBTEntry(uint64_t bndcfgu, uint64_t ptr, lldb::SBTarget &target, |
153 | llvm::Triple::ArchType arch, |
154 | lldb::SBCommandReturnObject &result, |
155 | lldb::SBError &error) { |
156 | lldb::addr_t bt_entry_addr; |
157 | size_t size; |
158 | if (!GetBTEntryAddr(bndcfgu, ptr, target, arch, size, bt_entry_addr, result, |
159 | error)) |
160 | return false; |
161 | |
162 | // bt_entry_v must have space to store the 4 elements of the BT entry (lower |
163 | // boundary, |
164 | // upper boundary, pointer value and meta data), which all have the same size |
165 | // 'size'. |
166 | // |
167 | std::vector<uint8_t> bt_entry_v(size * 4); |
168 | size_t ret = target.GetProcess().ReadMemory( |
169 | addr: bt_entry_addr, buf: static_cast<void *>(bt_entry_v.data()), size: size * 4, error); |
170 | |
171 | if ((ret != (size * 4)) || !error.Success()) { |
172 | result.SetError("Unsuccessful. Failed access to BT entry." ); |
173 | result.SetStatus(lldb::eReturnStatusFailed); |
174 | return false; |
175 | } |
176 | |
177 | lldb::addr_t lbound; |
178 | lldb::addr_t ubound; |
179 | uint64_t value; |
180 | uint64_t meta; |
181 | lldb::SBData data; |
182 | data.SetData(error, buf: bt_entry_v.data(), size: bt_entry_v.size(), |
183 | endian: target.GetByteOrder(), addr_size: size); |
184 | lbound = data.GetAddress(error, offset: size * 0); |
185 | ubound = data.GetAddress(error, offset: size * 1); |
186 | value = data.GetAddress(error, offset: size * 2); |
187 | meta = data.GetAddress(error, offset: size * 3); |
188 | // ubound is stored as one's complement. |
189 | if (arch == llvm::Triple::ArchType::x86) { |
190 | ubound = (~ubound) & 0x00000000FFFFFFFF; |
191 | } else { |
192 | ubound = ~ubound; |
193 | } |
194 | |
195 | if (!error.Success()) { |
196 | result.SetError("Failed access to BT entry." ); |
197 | return false; |
198 | } |
199 | |
200 | PrintBTEntry(lbound, ubound, value, meta, result); |
201 | |
202 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); |
203 | return true; |
204 | } |
205 | |
206 | static std::vector<uint8_t> uIntToU8(uint64_t input, size_t size) { |
207 | std::vector<uint8_t> output; |
208 | for (size_t i = 0; i < size; i++) |
209 | output.push_back( |
210 | x: static_cast<uint8_t>((input & (0xFFULL << (i * 8))) >> (i * 8))); |
211 | |
212 | return output; |
213 | } |
214 | |
215 | static bool SetBTEntry(uint64_t bndcfgu, uint64_t ptr, lldb::addr_t lbound, |
216 | lldb::addr_t ubound, lldb::SBTarget &target, |
217 | llvm::Triple::ArchType arch, |
218 | lldb::SBCommandReturnObject &result, |
219 | lldb::SBError &error) { |
220 | lldb::addr_t bt_entry_addr; |
221 | size_t size; |
222 | |
223 | if (!GetBTEntryAddr(bndcfgu, ptr, target, arch, size, bt_entry_addr, result, |
224 | error)) |
225 | return false; |
226 | |
227 | // bt_entry_v must have space to store only 2 elements of the BT Entry, the |
228 | // lower boundary and the upper boundary, which both have size 'size'. |
229 | // |
230 | std::vector<uint8_t> bt_entry_v(size * 2); |
231 | |
232 | std::vector<uint8_t> lbound_v = uIntToU8(input: lbound, size); |
233 | bt_entry_v.insert(position: bt_entry_v.begin(), first: lbound_v.begin(), last: lbound_v.end()); |
234 | std::vector<uint8_t> ubound_v = uIntToU8(input: ~ubound, size); |
235 | bt_entry_v.insert(position: bt_entry_v.begin() + size, first: ubound_v.begin(), |
236 | last: ubound_v.end()); |
237 | |
238 | size_t ret = target.GetProcess().WriteMemory( |
239 | addr: bt_entry_addr, buf: (void *)(bt_entry_v.data()), size: size * 2, error); |
240 | if ((ret != (size * 2)) || !error.Success()) { |
241 | result.SetError("Failed access to BT entry." ); |
242 | result.SetStatus(lldb::eReturnStatusFailed); |
243 | return false; |
244 | } |
245 | |
246 | result.SetStatus(lldb::eReturnStatusSuccessFinishResult); |
247 | return true; |
248 | } |
249 | |
250 | static bool GetInitInfo(lldb::SBDebugger debugger, lldb::SBTarget &target, |
251 | llvm::Triple::ArchType &arch, uint64_t &bndcfgu, |
252 | char *arg, uint64_t &ptr, |
253 | lldb::SBCommandReturnObject &result, |
254 | lldb::SBError &error) { |
255 | target = debugger.GetSelectedTarget(); |
256 | if (!target.IsValid()) { |
257 | result.SetError("Invalid target." ); |
258 | result.SetStatus(lldb::eReturnStatusFailed); |
259 | return false; |
260 | } |
261 | |
262 | const std::string triple_s(target.GetTriple()); |
263 | const llvm::Triple triple(triple_s); |
264 | |
265 | arch = triple.getArch(); |
266 | |
267 | if ((arch != llvm::Triple::ArchType::x86) && |
268 | (arch != llvm::Triple::ArchType::x86_64)) { |
269 | result.SetError("Platform not supported." ); |
270 | result.SetStatus(lldb::eReturnStatusFailed); |
271 | return false; |
272 | } |
273 | |
274 | lldb::SBFrame frame = |
275 | target.GetProcess().GetSelectedThread().GetSelectedFrame(); |
276 | if (!frame.IsValid()) { |
277 | result.SetError("No valid process, thread or frame." ); |
278 | result.SetStatus(lldb::eReturnStatusFailed); |
279 | return false; |
280 | } |
281 | |
282 | lldb::SBValue bndcfgu_val = frame.FindRegister(name: "bndcfgu" ); |
283 | if (!bndcfgu_val.IsValid()) { |
284 | result.SetError("Cannot access register BNDCFGU. Does the target support " |
285 | "Intel(R) Memory Protection Extensions (Intel(R) MPX)?" ); |
286 | result.SetStatus(lldb::eReturnStatusFailed); |
287 | return false; |
288 | } |
289 | |
290 | lldb::SBData bndcfgu_data = bndcfgu_val.GetData(); |
291 | bndcfgu = bndcfgu_data.GetUnsignedInt64(error, offset: 0); |
292 | if (!error.Success()) { |
293 | result.SetError(error, fallback_error_cstr: "Invalid read of register BNDCFGU." ); |
294 | return false; |
295 | } |
296 | |
297 | if (!GetPtr(cptr: arg, ptr, frame, result)) |
298 | return false; |
299 | |
300 | return true; |
301 | } |
302 | |
303 | class MPXTableShow : public lldb::SBCommandPluginInterface { |
304 | public: |
305 | bool DoExecute(lldb::SBDebugger debugger, char **command, |
306 | lldb::SBCommandReturnObject &result) override { |
307 | |
308 | if (command) { |
309 | int arg_c = 0; |
310 | char *arg; |
311 | |
312 | while (*command) { |
313 | if (arg_c >= 1) { |
314 | result.SetError("Too many arguments. See help." ); |
315 | result.SetStatus(lldb::eReturnStatusFailed); |
316 | return false; |
317 | } |
318 | arg_c++; |
319 | arg = *command; |
320 | command++; |
321 | } |
322 | |
323 | if (!debugger.IsValid()) { |
324 | result.SetError("Invalid debugger." ); |
325 | result.SetStatus(lldb::eReturnStatusFailed); |
326 | return false; |
327 | } |
328 | |
329 | lldb::SBTarget target; |
330 | llvm::Triple::ArchType arch; |
331 | lldb::SBError error; |
332 | uint64_t bndcfgu; |
333 | uint64_t ptr; |
334 | |
335 | if (!GetInitInfo(debugger, target, arch, bndcfgu, arg, ptr, result, |
336 | error)) |
337 | return false; |
338 | |
339 | return GetBTEntry(bndcfgu, ptr, target, arch, result, error); |
340 | } |
341 | |
342 | result.SetError("Too few arguments. See help." ); |
343 | result.SetStatus(lldb::eReturnStatusFailed); |
344 | return false; |
345 | } |
346 | }; |
347 | |
348 | class MPXTableSet : public lldb::SBCommandPluginInterface { |
349 | public: |
350 | bool DoExecute(lldb::SBDebugger debugger, char **command, |
351 | lldb::SBCommandReturnObject &result) override { |
352 | |
353 | if (command) { |
354 | int arg_c = 0; |
355 | char *arg[3]; |
356 | |
357 | while (*command) { |
358 | arg[arg_c] = *command; |
359 | command++; |
360 | arg_c++; |
361 | } |
362 | |
363 | if (arg_c != 3) { |
364 | result.SetError("Wrong arguments. See help." ); |
365 | return false; |
366 | } |
367 | |
368 | if (!debugger.IsValid()) { |
369 | result.SetError("Invalid debugger." ); |
370 | return false; |
371 | } |
372 | |
373 | lldb::SBTarget target; |
374 | llvm::Triple::ArchType arch; |
375 | lldb::SBError error; |
376 | uint64_t bndcfgu; |
377 | uint64_t ptr; |
378 | |
379 | if (!GetInitInfo(debugger, target, arch, bndcfgu, arg: arg[0], ptr, result, |
380 | error)) |
381 | return false; |
382 | |
383 | char *endptr; |
384 | errno = 0; |
385 | uint64_t lbound = std::strtoul(nptr: arg[1], endptr: &endptr, base: 16); |
386 | if (endptr == arg[1] || errno == ERANGE) { |
387 | result.SetError("Lower Bound: bad argument format." ); |
388 | errno = 0; |
389 | return false; |
390 | } |
391 | |
392 | uint64_t ubound = std::strtoul(nptr: arg[2], endptr: &endptr, base: 16); |
393 | if (endptr == arg[1] || errno == ERANGE) { |
394 | result.SetError("Upper Bound: bad argument format." ); |
395 | errno = 0; |
396 | return false; |
397 | } |
398 | |
399 | return SetBTEntry(bndcfgu, ptr, lbound, ubound, target, arch, result, |
400 | error); |
401 | } |
402 | |
403 | result.SetError("Too few arguments. See help." ); |
404 | return false; |
405 | } |
406 | }; |
407 | |
408 | bool MPXPluginInitialize(lldb::SBDebugger &debugger) { |
409 | lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); |
410 | lldb::SBCommand mpxTable = interpreter.AddMultiwordCommand( |
411 | name: "mpx-table" , help: "A utility to access the Intel(R) MPX table entries." ); |
412 | |
413 | const char *mpx_show_help = "Show the Intel(R) MPX table entry of a pointer." |
414 | "\nmpx-table show <pointer>" ; |
415 | mpxTable.AddCommand(name: "show" , impl: new MPXTableShow(), help: mpx_show_help); |
416 | |
417 | const char *mpx_set_help = |
418 | "Set the Intel(R) MPX table entry of a pointer.\n" |
419 | "mpx-table set <pointer> <lower bound> <upper bound>" ; |
420 | mpxTable.AddCommand(name: "set" , impl: new MPXTableSet(), help: mpx_set_help); |
421 | |
422 | return true; |
423 | } |
424 | |