xref: /openbsd-src/gnu/llvm/lldb/source/Plugins/Process/FreeBSDKernel/ProcessFreeBSDKernel.cpp (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1*f6aab3d8Srobert //===-- ProcessFreeBSDKernel.cpp ------------------------------------------===//
2*f6aab3d8Srobert //
3*f6aab3d8Srobert // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*f6aab3d8Srobert // See https://llvm.org/LICENSE.txt for license information.
5*f6aab3d8Srobert // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*f6aab3d8Srobert //
7*f6aab3d8Srobert //===----------------------------------------------------------------------===//
8*f6aab3d8Srobert 
9*f6aab3d8Srobert #include "lldb/Core/Module.h"
10*f6aab3d8Srobert #include "lldb/Core/PluginManager.h"
11*f6aab3d8Srobert #include "lldb/Target/DynamicLoader.h"
12*f6aab3d8Srobert 
13*f6aab3d8Srobert #include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h"
14*f6aab3d8Srobert #include "ProcessFreeBSDKernel.h"
15*f6aab3d8Srobert #include "ThreadFreeBSDKernel.h"
16*f6aab3d8Srobert 
17*f6aab3d8Srobert #if LLDB_ENABLE_FBSDVMCORE
18*f6aab3d8Srobert #include <fvc.h>
19*f6aab3d8Srobert #endif
20*f6aab3d8Srobert #if defined(__FreeBSD__)
21*f6aab3d8Srobert #include <kvm.h>
22*f6aab3d8Srobert #endif
23*f6aab3d8Srobert 
24*f6aab3d8Srobert using namespace lldb;
25*f6aab3d8Srobert using namespace lldb_private;
26*f6aab3d8Srobert 
27*f6aab3d8Srobert LLDB_PLUGIN_DEFINE(ProcessFreeBSDKernel)
28*f6aab3d8Srobert 
29*f6aab3d8Srobert namespace {
30*f6aab3d8Srobert 
31*f6aab3d8Srobert #if LLDB_ENABLE_FBSDVMCORE
32*f6aab3d8Srobert class ProcessFreeBSDKernelFVC : public ProcessFreeBSDKernel {
33*f6aab3d8Srobert public:
34*f6aab3d8Srobert   ProcessFreeBSDKernelFVC(lldb::TargetSP target_sp, lldb::ListenerSP listener,
35*f6aab3d8Srobert                           fvc_t *fvc);
36*f6aab3d8Srobert 
37*f6aab3d8Srobert   ~ProcessFreeBSDKernelFVC();
38*f6aab3d8Srobert 
39*f6aab3d8Srobert   size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
40*f6aab3d8Srobert                       lldb_private::Status &error) override;
41*f6aab3d8Srobert 
42*f6aab3d8Srobert private:
43*f6aab3d8Srobert   fvc_t *m_fvc;
44*f6aab3d8Srobert 
45*f6aab3d8Srobert   const char *GetError();
46*f6aab3d8Srobert };
47*f6aab3d8Srobert #endif // LLDB_ENABLE_FBSDVMCORE
48*f6aab3d8Srobert 
49*f6aab3d8Srobert #if defined(__FreeBSD__)
50*f6aab3d8Srobert class ProcessFreeBSDKernelKVM : public ProcessFreeBSDKernel {
51*f6aab3d8Srobert public:
52*f6aab3d8Srobert   ProcessFreeBSDKernelKVM(lldb::TargetSP target_sp, lldb::ListenerSP listener,
53*f6aab3d8Srobert                           kvm_t *fvc);
54*f6aab3d8Srobert 
55*f6aab3d8Srobert   ~ProcessFreeBSDKernelKVM();
56*f6aab3d8Srobert 
57*f6aab3d8Srobert   size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
58*f6aab3d8Srobert                       lldb_private::Status &error) override;
59*f6aab3d8Srobert 
60*f6aab3d8Srobert private:
61*f6aab3d8Srobert   kvm_t *m_kvm;
62*f6aab3d8Srobert 
63*f6aab3d8Srobert   const char *GetError();
64*f6aab3d8Srobert };
65*f6aab3d8Srobert #endif // defined(__FreeBSD__)
66*f6aab3d8Srobert 
67*f6aab3d8Srobert } // namespace
68*f6aab3d8Srobert 
ProcessFreeBSDKernel(lldb::TargetSP target_sp,ListenerSP listener_sp)69*f6aab3d8Srobert ProcessFreeBSDKernel::ProcessFreeBSDKernel(lldb::TargetSP target_sp,
70*f6aab3d8Srobert                                            ListenerSP listener_sp)
71*f6aab3d8Srobert     : PostMortemProcess(target_sp, listener_sp) {}
72*f6aab3d8Srobert 
CreateInstance(lldb::TargetSP target_sp,ListenerSP listener_sp,const FileSpec * crash_file,bool can_connect)73*f6aab3d8Srobert lldb::ProcessSP ProcessFreeBSDKernel::CreateInstance(lldb::TargetSP target_sp,
74*f6aab3d8Srobert                                                      ListenerSP listener_sp,
75*f6aab3d8Srobert                                                      const FileSpec *crash_file,
76*f6aab3d8Srobert                                                      bool can_connect) {
77*f6aab3d8Srobert   ModuleSP executable = target_sp->GetExecutableModule();
78*f6aab3d8Srobert   if (crash_file && !can_connect && executable) {
79*f6aab3d8Srobert #if LLDB_ENABLE_FBSDVMCORE
80*f6aab3d8Srobert     fvc_t *fvc =
81*f6aab3d8Srobert         fvc_open(executable->GetFileSpec().GetPath().c_str(),
82*f6aab3d8Srobert                  crash_file->GetPath().c_str(), nullptr, nullptr, nullptr);
83*f6aab3d8Srobert     if (fvc)
84*f6aab3d8Srobert       return std::make_shared<ProcessFreeBSDKernelFVC>(target_sp, listener_sp,
85*f6aab3d8Srobert                                                        fvc);
86*f6aab3d8Srobert #endif
87*f6aab3d8Srobert 
88*f6aab3d8Srobert #if defined(__FreeBSD__)
89*f6aab3d8Srobert     kvm_t *kvm =
90*f6aab3d8Srobert         kvm_open2(executable->GetFileSpec().GetPath().c_str(),
91*f6aab3d8Srobert                   crash_file->GetPath().c_str(), O_RDONLY, nullptr, nullptr);
92*f6aab3d8Srobert     if (kvm)
93*f6aab3d8Srobert       return std::make_shared<ProcessFreeBSDKernelKVM>(target_sp, listener_sp,
94*f6aab3d8Srobert                                                        kvm);
95*f6aab3d8Srobert #endif
96*f6aab3d8Srobert   }
97*f6aab3d8Srobert   return nullptr;
98*f6aab3d8Srobert }
99*f6aab3d8Srobert 
Initialize()100*f6aab3d8Srobert void ProcessFreeBSDKernel::Initialize() {
101*f6aab3d8Srobert   static llvm::once_flag g_once_flag;
102*f6aab3d8Srobert 
103*f6aab3d8Srobert   llvm::call_once(g_once_flag, []() {
104*f6aab3d8Srobert     PluginManager::RegisterPlugin(GetPluginNameStatic(),
105*f6aab3d8Srobert                                   GetPluginDescriptionStatic(), CreateInstance);
106*f6aab3d8Srobert   });
107*f6aab3d8Srobert }
108*f6aab3d8Srobert 
Terminate()109*f6aab3d8Srobert void ProcessFreeBSDKernel::Terminate() {
110*f6aab3d8Srobert   PluginManager::UnregisterPlugin(ProcessFreeBSDKernel::CreateInstance);
111*f6aab3d8Srobert }
112*f6aab3d8Srobert 
DoDestroy()113*f6aab3d8Srobert Status ProcessFreeBSDKernel::DoDestroy() { return Status(); }
114*f6aab3d8Srobert 
CanDebug(lldb::TargetSP target_sp,bool plugin_specified_by_name)115*f6aab3d8Srobert bool ProcessFreeBSDKernel::CanDebug(lldb::TargetSP target_sp,
116*f6aab3d8Srobert                                     bool plugin_specified_by_name) {
117*f6aab3d8Srobert   return true;
118*f6aab3d8Srobert }
119*f6aab3d8Srobert 
RefreshStateAfterStop()120*f6aab3d8Srobert void ProcessFreeBSDKernel::RefreshStateAfterStop() {}
121*f6aab3d8Srobert 
DoUpdateThreadList(ThreadList & old_thread_list,ThreadList & new_thread_list)122*f6aab3d8Srobert bool ProcessFreeBSDKernel::DoUpdateThreadList(ThreadList &old_thread_list,
123*f6aab3d8Srobert                                               ThreadList &new_thread_list) {
124*f6aab3d8Srobert   if (old_thread_list.GetSize(false) == 0) {
125*f6aab3d8Srobert     // Make up the thread the first time this is called so we can set our one
126*f6aab3d8Srobert     // and only core thread state up.
127*f6aab3d8Srobert 
128*f6aab3d8Srobert     // We cannot construct a thread without a register context as that crashes
129*f6aab3d8Srobert     // LLDB but we can construct a process without threads to provide minimal
130*f6aab3d8Srobert     // memory reading support.
131*f6aab3d8Srobert     switch (GetTarget().GetArchitecture().GetMachine()) {
132*f6aab3d8Srobert     case llvm::Triple::aarch64:
133*f6aab3d8Srobert     case llvm::Triple::x86:
134*f6aab3d8Srobert     case llvm::Triple::x86_64:
135*f6aab3d8Srobert       break;
136*f6aab3d8Srobert     default:
137*f6aab3d8Srobert       return false;
138*f6aab3d8Srobert     }
139*f6aab3d8Srobert 
140*f6aab3d8Srobert     Status error;
141*f6aab3d8Srobert 
142*f6aab3d8Srobert     // struct field offsets are written as symbols so that we don't have
143*f6aab3d8Srobert     // to figure them out ourselves
144*f6aab3d8Srobert     int32_t offset_p_list = ReadSignedIntegerFromMemory(
145*f6aab3d8Srobert         FindSymbol("proc_off_p_list"), 4, -1, error);
146*f6aab3d8Srobert     int32_t offset_p_pid =
147*f6aab3d8Srobert         ReadSignedIntegerFromMemory(FindSymbol("proc_off_p_pid"), 4, -1, error);
148*f6aab3d8Srobert     int32_t offset_p_threads = ReadSignedIntegerFromMemory(
149*f6aab3d8Srobert         FindSymbol("proc_off_p_threads"), 4, -1, error);
150*f6aab3d8Srobert     int32_t offset_p_comm = ReadSignedIntegerFromMemory(
151*f6aab3d8Srobert         FindSymbol("proc_off_p_comm"), 4, -1, error);
152*f6aab3d8Srobert 
153*f6aab3d8Srobert     int32_t offset_td_tid = ReadSignedIntegerFromMemory(
154*f6aab3d8Srobert         FindSymbol("thread_off_td_tid"), 4, -1, error);
155*f6aab3d8Srobert     int32_t offset_td_plist = ReadSignedIntegerFromMemory(
156*f6aab3d8Srobert         FindSymbol("thread_off_td_plist"), 4, -1, error);
157*f6aab3d8Srobert     int32_t offset_td_pcb = ReadSignedIntegerFromMemory(
158*f6aab3d8Srobert         FindSymbol("thread_off_td_pcb"), 4, -1, error);
159*f6aab3d8Srobert     int32_t offset_td_oncpu = ReadSignedIntegerFromMemory(
160*f6aab3d8Srobert         FindSymbol("thread_off_td_oncpu"), 4, -1, error);
161*f6aab3d8Srobert     int32_t offset_td_name = ReadSignedIntegerFromMemory(
162*f6aab3d8Srobert         FindSymbol("thread_off_td_name"), 4, -1, error);
163*f6aab3d8Srobert 
164*f6aab3d8Srobert     // fail if we were not able to read any of the offsets
165*f6aab3d8Srobert     if (offset_p_list == -1 || offset_p_pid == -1 || offset_p_threads == -1 ||
166*f6aab3d8Srobert         offset_p_comm == -1 || offset_td_tid == -1 || offset_td_plist == -1 ||
167*f6aab3d8Srobert         offset_td_pcb == -1 || offset_td_oncpu == -1 || offset_td_name == -1)
168*f6aab3d8Srobert       return false;
169*f6aab3d8Srobert 
170*f6aab3d8Srobert     // dumptid contains the thread-id of the crashing thread
171*f6aab3d8Srobert     // dumppcb contains its PCB
172*f6aab3d8Srobert     int32_t dumptid =
173*f6aab3d8Srobert         ReadSignedIntegerFromMemory(FindSymbol("dumptid"), 4, -1, error);
174*f6aab3d8Srobert     lldb::addr_t dumppcb = FindSymbol("dumppcb");
175*f6aab3d8Srobert 
176*f6aab3d8Srobert     // stoppcbs is an array of PCBs on all CPUs
177*f6aab3d8Srobert     // each element is of size pcb_size
178*f6aab3d8Srobert     int32_t pcbsize =
179*f6aab3d8Srobert         ReadSignedIntegerFromMemory(FindSymbol("pcb_size"), 4, -1, error);
180*f6aab3d8Srobert     lldb::addr_t stoppcbs = FindSymbol("stoppcbs");
181*f6aab3d8Srobert 
182*f6aab3d8Srobert     // from FreeBSD sys/param.h
183*f6aab3d8Srobert     constexpr size_t fbsd_maxcomlen = 19;
184*f6aab3d8Srobert 
185*f6aab3d8Srobert     // iterate through a linked list of all processes
186*f6aab3d8Srobert     // allproc is a pointer to the first list element, p_list field
187*f6aab3d8Srobert     // (found at offset_p_list) specifies the next element
188*f6aab3d8Srobert     for (lldb::addr_t proc =
189*f6aab3d8Srobert              ReadPointerFromMemory(FindSymbol("allproc"), error);
190*f6aab3d8Srobert          proc != 0 && proc != LLDB_INVALID_ADDRESS;
191*f6aab3d8Srobert          proc = ReadPointerFromMemory(proc + offset_p_list, error)) {
192*f6aab3d8Srobert       int32_t pid =
193*f6aab3d8Srobert           ReadSignedIntegerFromMemory(proc + offset_p_pid, 4, -1, error);
194*f6aab3d8Srobert       // process' command-line string
195*f6aab3d8Srobert       char comm[fbsd_maxcomlen + 1];
196*f6aab3d8Srobert       ReadCStringFromMemory(proc + offset_p_comm, comm, sizeof(comm), error);
197*f6aab3d8Srobert 
198*f6aab3d8Srobert       // iterate through a linked list of all process' threads
199*f6aab3d8Srobert       // the initial thread is found in process' p_threads, subsequent
200*f6aab3d8Srobert       // elements are linked via td_plist field
201*f6aab3d8Srobert       for (lldb::addr_t td =
202*f6aab3d8Srobert                ReadPointerFromMemory(proc + offset_p_threads, error);
203*f6aab3d8Srobert            td != 0; td = ReadPointerFromMemory(td + offset_td_plist, error)) {
204*f6aab3d8Srobert         int32_t tid =
205*f6aab3d8Srobert             ReadSignedIntegerFromMemory(td + offset_td_tid, 4, -1, error);
206*f6aab3d8Srobert         lldb::addr_t pcb_addr =
207*f6aab3d8Srobert             ReadPointerFromMemory(td + offset_td_pcb, error);
208*f6aab3d8Srobert         // whether process was on CPU (-1 if not, otherwise CPU number)
209*f6aab3d8Srobert         int32_t oncpu =
210*f6aab3d8Srobert             ReadSignedIntegerFromMemory(td + offset_td_oncpu, 4, -2, error);
211*f6aab3d8Srobert         // thread name
212*f6aab3d8Srobert         char thread_name[fbsd_maxcomlen + 1];
213*f6aab3d8Srobert         ReadCStringFromMemory(td + offset_td_name, thread_name,
214*f6aab3d8Srobert                               sizeof(thread_name), error);
215*f6aab3d8Srobert 
216*f6aab3d8Srobert         // if we failed to read TID, ignore this thread
217*f6aab3d8Srobert         if (tid == -1)
218*f6aab3d8Srobert           continue;
219*f6aab3d8Srobert 
220*f6aab3d8Srobert         std::string thread_desc = llvm::formatv("(pid {0}) {1}", pid, comm);
221*f6aab3d8Srobert         if (*thread_name && strcmp(thread_name, comm)) {
222*f6aab3d8Srobert           thread_desc += '/';
223*f6aab3d8Srobert           thread_desc += thread_name;
224*f6aab3d8Srobert         }
225*f6aab3d8Srobert 
226*f6aab3d8Srobert         // roughly:
227*f6aab3d8Srobert         // 1. if the thread crashed, its PCB is going to be at "dumppcb"
228*f6aab3d8Srobert         // 2. if the thread was on CPU, its PCB is going to be on the CPU
229*f6aab3d8Srobert         // 3. otherwise, its PCB is in the thread struct
230*f6aab3d8Srobert         if (tid == dumptid) {
231*f6aab3d8Srobert           // NB: dumppcb can be LLDB_INVALID_ADDRESS if reading it failed
232*f6aab3d8Srobert           pcb_addr = dumppcb;
233*f6aab3d8Srobert           thread_desc += " (crashed)";
234*f6aab3d8Srobert         } else if (oncpu != -1) {
235*f6aab3d8Srobert           // if we managed to read stoppcbs and pcb_size, use them to find
236*f6aab3d8Srobert           // the correct PCB
237*f6aab3d8Srobert           if (stoppcbs != LLDB_INVALID_ADDRESS && pcbsize > 0)
238*f6aab3d8Srobert             pcb_addr = stoppcbs + oncpu * pcbsize;
239*f6aab3d8Srobert           else
240*f6aab3d8Srobert             pcb_addr = LLDB_INVALID_ADDRESS;
241*f6aab3d8Srobert           thread_desc += llvm::formatv(" (on CPU {0})", oncpu);
242*f6aab3d8Srobert         }
243*f6aab3d8Srobert 
244*f6aab3d8Srobert         ThreadSP thread_sp{
245*f6aab3d8Srobert             new ThreadFreeBSDKernel(*this, tid, pcb_addr, thread_desc)};
246*f6aab3d8Srobert         new_thread_list.AddThread(thread_sp);
247*f6aab3d8Srobert       }
248*f6aab3d8Srobert     }
249*f6aab3d8Srobert   } else {
250*f6aab3d8Srobert     const uint32_t num_threads = old_thread_list.GetSize(false);
251*f6aab3d8Srobert     for (uint32_t i = 0; i < num_threads; ++i)
252*f6aab3d8Srobert       new_thread_list.AddThread(old_thread_list.GetThreadAtIndex(i, false));
253*f6aab3d8Srobert   }
254*f6aab3d8Srobert   return new_thread_list.GetSize(false) > 0;
255*f6aab3d8Srobert }
256*f6aab3d8Srobert 
DoLoadCore()257*f6aab3d8Srobert Status ProcessFreeBSDKernel::DoLoadCore() {
258*f6aab3d8Srobert   // The core is already loaded by CreateInstance().
259*f6aab3d8Srobert   return Status();
260*f6aab3d8Srobert }
261*f6aab3d8Srobert 
GetDynamicLoader()262*f6aab3d8Srobert DynamicLoader *ProcessFreeBSDKernel::GetDynamicLoader() {
263*f6aab3d8Srobert   if (m_dyld_up.get() == nullptr)
264*f6aab3d8Srobert     m_dyld_up.reset(DynamicLoader::FindPlugin(
265*f6aab3d8Srobert         this, DynamicLoaderStatic::GetPluginNameStatic()));
266*f6aab3d8Srobert   return m_dyld_up.get();
267*f6aab3d8Srobert }
268*f6aab3d8Srobert 
FindSymbol(const char * name)269*f6aab3d8Srobert lldb::addr_t ProcessFreeBSDKernel::FindSymbol(const char *name) {
270*f6aab3d8Srobert   ModuleSP mod_sp = GetTarget().GetExecutableModule();
271*f6aab3d8Srobert   const Symbol *sym = mod_sp->FindFirstSymbolWithNameAndType(ConstString(name));
272*f6aab3d8Srobert   return sym ? sym->GetLoadAddress(&GetTarget()) : LLDB_INVALID_ADDRESS;
273*f6aab3d8Srobert }
274*f6aab3d8Srobert 
275*f6aab3d8Srobert #if LLDB_ENABLE_FBSDVMCORE
276*f6aab3d8Srobert 
ProcessFreeBSDKernelFVC(lldb::TargetSP target_sp,ListenerSP listener_sp,fvc_t * fvc)277*f6aab3d8Srobert ProcessFreeBSDKernelFVC::ProcessFreeBSDKernelFVC(lldb::TargetSP target_sp,
278*f6aab3d8Srobert                                                  ListenerSP listener_sp,
279*f6aab3d8Srobert                                                  fvc_t *fvc)
280*f6aab3d8Srobert     : ProcessFreeBSDKernel(target_sp, listener_sp), m_fvc(fvc) {}
281*f6aab3d8Srobert 
~ProcessFreeBSDKernelFVC()282*f6aab3d8Srobert ProcessFreeBSDKernelFVC::~ProcessFreeBSDKernelFVC() {
283*f6aab3d8Srobert   if (m_fvc)
284*f6aab3d8Srobert     fvc_close(m_fvc);
285*f6aab3d8Srobert }
286*f6aab3d8Srobert 
DoReadMemory(lldb::addr_t addr,void * buf,size_t size,Status & error)287*f6aab3d8Srobert size_t ProcessFreeBSDKernelFVC::DoReadMemory(lldb::addr_t addr, void *buf,
288*f6aab3d8Srobert                                              size_t size, Status &error) {
289*f6aab3d8Srobert   ssize_t rd = 0;
290*f6aab3d8Srobert   rd = fvc_read(m_fvc, addr, buf, size);
291*f6aab3d8Srobert   if (rd < 0 || static_cast<size_t>(rd) != size) {
292*f6aab3d8Srobert     error.SetErrorStringWithFormat("Reading memory failed: %s", GetError());
293*f6aab3d8Srobert     return rd > 0 ? rd : 0;
294*f6aab3d8Srobert   }
295*f6aab3d8Srobert   return rd;
296*f6aab3d8Srobert }
297*f6aab3d8Srobert 
GetError()298*f6aab3d8Srobert const char *ProcessFreeBSDKernelFVC::GetError() { return fvc_geterr(m_fvc); }
299*f6aab3d8Srobert 
300*f6aab3d8Srobert #endif // LLDB_ENABLE_FBSDVMCORE
301*f6aab3d8Srobert 
302*f6aab3d8Srobert #if defined(__FreeBSD__)
303*f6aab3d8Srobert 
ProcessFreeBSDKernelKVM(lldb::TargetSP target_sp,ListenerSP listener_sp,kvm_t * fvc)304*f6aab3d8Srobert ProcessFreeBSDKernelKVM::ProcessFreeBSDKernelKVM(lldb::TargetSP target_sp,
305*f6aab3d8Srobert                                                  ListenerSP listener_sp,
306*f6aab3d8Srobert                                                  kvm_t *fvc)
307*f6aab3d8Srobert     : ProcessFreeBSDKernel(target_sp, listener_sp), m_kvm(fvc) {}
308*f6aab3d8Srobert 
~ProcessFreeBSDKernelKVM()309*f6aab3d8Srobert ProcessFreeBSDKernelKVM::~ProcessFreeBSDKernelKVM() {
310*f6aab3d8Srobert   if (m_kvm)
311*f6aab3d8Srobert     kvm_close(m_kvm);
312*f6aab3d8Srobert }
313*f6aab3d8Srobert 
DoReadMemory(lldb::addr_t addr,void * buf,size_t size,Status & error)314*f6aab3d8Srobert size_t ProcessFreeBSDKernelKVM::DoReadMemory(lldb::addr_t addr, void *buf,
315*f6aab3d8Srobert                                              size_t size, Status &error) {
316*f6aab3d8Srobert   ssize_t rd = 0;
317*f6aab3d8Srobert   rd = kvm_read2(m_kvm, addr, buf, size);
318*f6aab3d8Srobert   if (rd < 0 || static_cast<size_t>(rd) != size) {
319*f6aab3d8Srobert     error.SetErrorStringWithFormat("Reading memory failed: %s", GetError());
320*f6aab3d8Srobert     return rd > 0 ? rd : 0;
321*f6aab3d8Srobert   }
322*f6aab3d8Srobert   return rd;
323*f6aab3d8Srobert }
324*f6aab3d8Srobert 
GetError()325*f6aab3d8Srobert const char *ProcessFreeBSDKernelKVM::GetError() { return kvm_geterr(m_kvm); }
326*f6aab3d8Srobert 
327*f6aab3d8Srobert #endif // defined(__FreeBSD__)
328