VBoxClient.cpp
Go to the documentation of this file.
1 #include "StdAfx.h"
2 #include "VBoxClient.h"
3 #include "../../GDBServerFoundation/HexHelpers.h"
4 #include <bzscore/path.h>
5 
6 using namespace VBoxGDB;
7 using namespace BazisLib;
8 
9 VBoxClient::VBoxClient( const Network::InternetAddress &addr)
10  : m_Socket(addr)
11  , m_b64Bit(false)
12  , m_CPUCount(0)
13  , m_BreakpointIDOfLastStopEvent(-1)
14  , m_LastBlockReadAddr(-1LL)
15  , m_TargetAddr(addr)
16 {
17  m_BufferAllocationSize = 1024 * 1024;
18  m_Socket.SetNoDelay(true);
19 
20  //m_Socket.StartLogging(L"VBoxDebugger.log");
21 }
22 
24 {
25  Disconnect();
26 }
27 
28 bool VBoxGDB::VBoxClient::RecvTillCommandPrompt()
29 {
30  const char debugPrompt[] = "VBoxDbg>";
31  m_Buffer.SetLength(0);
32  size_t done = m_Socket.RecvToMarker(m_Buffer.PreAllocate(m_BufferAllocationSize, false), m_BufferAllocationSize, debugPrompt, sizeof(debugPrompt) - 1);
33  if (done == 0 || done == -1 || done < (sizeof(debugPrompt) - 1))
34  return false;
35  m_Buffer.SetLength(done - (sizeof(debugPrompt) - 1));
36  return true;
37 }
38 
40 {
41  if (!RecvTillCommandPrompt())
42  return false;
43  std::vector<DynamicStringA> result;
44 
45 #ifdef USE_REG_WORKAROUND_DLL
46  TCHAR tsz[MAX_PATH];
47  GetModuleFileName(GetModuleHandle(0), tsz, __countof(tsz));
48 
49  DynamicStringA str = "loadplugin ";
50  str += StringToANSIString(Path::GetDirectoryName(TempStrPointerWrapper(tsz))) + "/regfix64.dll";
51  for (size_t i = 0; i < str.length(); i++)
52  if (str[i] == '\\')
53  str[i] = '/';
54  result = ExecuteCommand(str.c_str());
55  if (!IsSuccessfulReply(result))
56  {
57  ReportError("Cannot load regfix64.dll into VirtualBox. Debugging won't be possible.");
58  return false;
59  }
60 #endif
61 
62  result = ExecuteCommand("cpu ffff");
63  for (size_t i = 0; i < result.size(); i++)
64  {
65  const char szPattern[] = "is out of range! Highest ID is ";
66  size_t idx = result[i].find(szPattern);
67  if (idx == -1)
68  continue;
69  m_CPUCount = atoi(result[i].c_str() + idx + sizeof(szPattern) - 1);
70  break;
71  }
72 
73  if (!m_CPUCount)
74  m_CPUCount = 1;
75 
76  //TODO: detect x86/x64 mode
77 
78  return true;
79 }
80 
81 std::vector<DynamicStringA> VBoxGDB::VBoxClient::ExecuteCommand( const char *pCommand )
82 {
83  std::string str = pCommand;
84  str += "\n";
85  //VirtualBox debugger has a bug: it won't recognize the command if the "\n" suffix was sent separately
86  m_Socket.Send(str.c_str(), str.length());
87 
88  std::vector<DynamicStringA> result;
89  if (!RecvTillCommandPrompt())
90  return result;
91 
92  for each(const TempStringA &str in m_Buffer.Split("\r\n"))
93  result.push_back(str);
94 
95  return result;
96 }
97 
98 void VBoxGDB::VBoxClient::ReportError( const char *pErrorDescription )
99 {
100  printf("Error: %s\n", pErrorDescription);
101 }
102 
104 {
105  m_Socket.Send("stop\n", 5);
106 }
107 
108 static bool FindNextRegVal(const DynamicStringA &str, size_t &initial, TempStringA &regName, TempStringA &regVal)
109 {
110  size_t idx = str.find('=', initial);
111  if (idx == -1 || !idx)
112  return false;
113 
114  size_t regStart = 0, regEnd = idx;
115  for (;;)
116  {
117  regStart = str.rfind(' ', regEnd - 1);
118  if (regStart == -1)
119  regStart = 0;
120  else
121  regStart++;
122 
123  if (regEnd != regStart || !regStart)
124  break;
125 
126  regEnd = regStart - 1;
127  }
128 
129 
130  regName = str.substr(regStart, regEnd - regStart);
131 
132  size_t end = str.find_first_not_of("0x123456789ABCDEFabcdef", idx + 1);
133  if (end == -1)
134  end = str.length();
135 
136  regVal = str.substr(idx + 1, end - idx - 1);
137  initial = end;
138  return true;
139 }
140 
141 std::map<DynamicStringA, ULONGLONG> VBoxGDB::VBoxClient::ReadRegisters()
142 {
143  std::map<DynamicStringA, ULONGLONG> result;
144  std::vector<DynamicStringA> lines = ExecuteCommand(m_b64Bit ? "rg64" : "rg32");
145  for (size_t i = 0; i < lines.size(); i++)
146  {
147  size_t initial = 0;
148  for( ;;)
149  {
150  TempStringA regName(ConstStringA(""));
151  TempStringA regVal(ConstStringA(""));
152  if (!FindNextRegVal(lines[i], initial, regName, regVal))
153  break;
154 
155  result[regName] = GDBServerFoundation::HexHelpers::ParseHexString<ULONGLONG>(regVal);
156  }
157  }
158 
159  return result;
160 }
161 
162 bool VBoxGDB::VBoxClient::WriteRegister( const char *pName, ULONGLONG value )
163 {
164  DynamicStringA strCmd;
165 #ifdef USE_REG_WORKAROUND_DLL
166  const char *pRegCmd = ".r";
167 #else
168  const char *pRegCmd = "r";
169 #endif
170 
171  if (!m_b64Bit)
172  strCmd.Format("%s %s 0x%08X", pRegCmd, pName, (unsigned)value);
173  else
174  strCmd.Format("%s %s 0x%016I64x", pRegCmd, pName, value);
175 
176  std::vector<DynamicStringA> result = ExecuteCommand(strCmd.c_str());
177  return IsSuccessfulReply(result);
178 }
179 
180 static bool StartsWith(const TempStringA &str, const char *pStr)
181 {
182  size_t len = strlen(pStr);
183  if (str.length() < len)
184  return false;
185  return !memcmp(str.GetConstBuffer(), pStr, len);
186 }
187 
189 {
190  static const char szEvent[] = "dbgf event:";
191  static const char szWarning[] = "warning: The VM is already halted...";
192  static const char szBreakpoint[] = "Breakpoint ";
193 
194  for (;;)
195  {
196  if (!RecvTillCommandPrompt())
197  return false;
198 
199  int idx = m_Buffer.find_first_not_of(" \r\n\t");
200  if (idx == -1)
201  continue;
202 
203  TempStringA &str = m_Buffer.substr(idx);
204  if (StartsWith(str, szEvent))
205  {
206  TempStringA evt = str.substr(sizeof(szEvent));
207  if (StartsWith(evt, szBreakpoint))
208  {
209  m_BreakpointIDOfLastStopEvent = atoi(evt.GetConstBuffer() + sizeof(szBreakpoint) - 1);
210  AdjustPCAfterBreakpoint(m_BreakpointIDOfLastStopEvent);
211  }
212  else
213  m_BreakpointIDOfLastStopEvent = -1;
214  return true;
215  }
216  if (StartsWith(str, szWarning))
217  return true;
218 
219  continue;
220  }
221 }
222 
224 {
225  m_Socket.Send("exit\n", 5);
226  RecvTillCommandPrompt();
227  m_Socket.Close();
228 }
229 
230 bool VBoxGDB::VBoxClient::IsSuccessfulReply( const std::vector<DynamicStringA> &reply, bool report )
231 {
232  for (size_t i = 0; i < reply.size(); i++)
233  {
234  if (reply[i].find("error:") != -1)
235  {
236  if (report)
237  ReportError(reply[i].c_str());
238  return false;
239  }
240  }
241  return true;
242 }
243 
244 bool VBoxGDB::VBoxClient::ReadMemoryBlockAligned( ULONGLONG address, BazisLib::BasicBuffer &buffer )
245 {
246  if (address & 3)
247  {
248  ReportError("Error: trying to read from an unaligned address");
249  return false;
250  }
251 
252  DynamicStringA strCmd;
253  if (!m_b64Bit)
254  strCmd.Format("dd 0x%08X", (unsigned)address);
255  else
256  strCmd.Format("dd 0x%016I64x", address);
257 
258  std::vector<DynamicStringA> result = ExecuteCommand(strCmd.c_str());
259  buffer.SetSize(0);
260  ULONGLONG expectedAddr = address;
261  for (size_t i = 0; i < result.size(); i++)
262  {
263  const DynamicStringA &line = result[i];
264  int idx = line.find(':');
265  if (idx == -1 || !idx)
266  continue;
267  size_t lineStart = line.find_first_not_of(" \t");
268  if (lineStart == -1)
269  continue;
270  if (line[lineStart] != '%')
271  continue;
272 
273  ULONGLONG thisAddr = GDBServerFoundation::HexHelpers::ParseHexString<ULONGLONG>(line.substr(lineStart + 1, idx - lineStart - 1));
274  if (thisAddr != expectedAddr)
275  break;
276 
277  size_t start = idx + 1;
278  for (;;)
279  {
280  size_t numStart = line.find_first_not_of(' ', start);
281  if (numStart == -1)
282  break;
283  size_t numEnd = line.find(' ', numStart);
284  if (numEnd == -1)
285  numEnd = line.length();
286 
287  unsigned num = GDBServerFoundation::HexHelpers::ParseHexString<unsigned>(line.substr(numStart, numEnd - numStart));
288  C_ASSERT(sizeof(num) == 4);
289  buffer.append(&num, 4, 4096);
290  expectedAddr += 4;
291 
292  start = numEnd;
293  }
294 
295  }
296  return true;
297 }
298 
299 bool VBoxGDB::VBoxClient::WriteMemory( ULONGLONG address, const void *pData, size_t length )
300 {
301  m_LastBlockReadAddr = -1LL;
302  const char *p = (const char *)pData;
303  size_t remaining = length;
304  DynamicStringA strVal, strCmd;
305  while (remaining)
306  {
307  char mode;
308  size_t todo = 0;
309 
310  if (remaining >= 8)
311  {
312  mode = 'q';
313  strVal.Format("%016I64x", *((ULONGLONG *)p));
314  todo = 8;
315  }
316  else if (remaining >= 4)
317  {
318  mode = 'd';
319  strVal.Format("%08x", *((unsigned *)p));
320  todo = 4;
321  }
322  else if (remaining >= 2)
323  {
324  mode = 'w';
325  strVal.Format("%04x", *((unsigned short *)p));
326  todo = 2;
327  }
328  else
329  {
330  mode = 'b';
331  strVal.Format("%02x", *((unsigned char *)p));
332  todo = 1;
333  }
334 
335  if (m_b64Bit)
336  strCmd.Format("e%c 0x%016I64x %s", mode, address, strVal.c_str());
337  else
338  strCmd.Format("e%c 0x%08x %s", mode, (unsigned)address, strVal.c_str());
339 
340  std::vector<DynamicStringA> result = ExecuteCommand(strCmd.c_str());
341  if (!IsSuccessfulReply(result))
342  return false;
343 
344  remaining -= todo;
345  p += todo;
346  address += todo;
347  }
348  return true;
349 }
350 
351 bool VBoxGDB::VBoxClient::ReadMemory( ULONGLONG address, void *pData, size_t *pLength, bool allowCaching )
352 {
353  size_t requested = *pLength, done = 0;
354 
355  char *p = (char *)pData;
356 
357  if (allowCaching && m_LastBlockReadAddr != -1LL && (address >= m_LastBlockReadAddr) && (address < (m_LastBlockReadAddr + m_LastBlockReadBuffer.GetSize())))
358  {
359  size_t offsetInBuffer = (size_t)(address - m_LastBlockReadAddr);
360  size_t available = m_LastBlockReadBuffer.GetSize() - offsetInBuffer;
361 
362  size_t todo = min(requested, available);
363  memcpy(p, m_LastBlockReadBuffer.GetData(offsetInBuffer), todo);
364  done += todo;
365  p += todo;
366  address += todo;
367  }
368 
369  unsigned off = address & 3;
370  address &= ~3;
371 
372  while (done < requested)
373  {
374  if (!ReadMemoryBlockAligned(address, m_LastBlockReadBuffer))
375  break;
376  m_LastBlockReadAddr = address;
377  if (!m_LastBlockReadBuffer.GetSize())
378  break;
379  ASSERT(!(m_LastBlockReadBuffer.GetSize() & 3));
380 
381  size_t todo = min(requested - done, m_LastBlockReadBuffer.GetSize() - off);
382  memcpy(p, m_LastBlockReadBuffer.GetData(off), todo);
383  p += todo;
384  done += todo;
385  address += todo;
386  off = 0;
387  }
388 
389  *pLength = done;
390  return done != 0;
391 }
392 
394 {
395  if (!IsSuccessfulReply(ExecuteCommand("t")))
396  return false;
397  return WaitForStop();
398 }
399 
401 {
402  if (!IsSuccessfulReply(ExecuteCommand("g")))
403  return false;
404  return WaitForStop();
405 }
406 
408 {
409  std::vector<DynamicStringA> result = ExecuteCommand(DynamicStringA::sFormat("bp 0x%I64x", addr).c_str());
410  const char szText[] = "Set breakpoint ";
411  for (size_t i = 0; i < result.size(); i++)
412  {
413  off_t idx = result[i].find(szText);
414  if (idx == -1)
415  continue;
416  int bp = atoi(result[i].c_str() + idx + sizeof(szText) - 1);
417  if (!bp)
418  return -1;
419  m_BreakpointMap[bp] = addr;
420  return bp;
421  }
422  return -1;
423 }
424 
426 {
427  std::vector<DynamicStringA> result = ExecuteCommand(DynamicStringA::sFormat("bc %x", idx).c_str());
428  if (!IsSuccessfulReply(result))
429  return false;
430  BreakpointAddressMap::iterator it = m_BreakpointMap.find(idx);
431  if (it != m_BreakpointMap.end())
432  {
433  m_BreakpointMap.erase(it);
434  }
435  return true;
436 }
437 
438 void VBoxGDB::VBoxClient::AdjustPCAfterBreakpoint( int breakpointNumber )
439 {
440  ULONGLONG val = GetCurrentPC();
441  BreakpointAddressMap::iterator it = m_BreakpointMap.find(breakpointNumber);
442  if (it == m_BreakpointMap.end())
443  return;
444  if (val == (it->second + 1))
445  {
446  //It looks like VirtualBox is using its own int3 handler to detect breakpoints.
447  //This int3 handler determines the address of the breakpoint from the return address in the stack,
448  //that actually points to the next instruction.
449 
450  //While this makes perfect sense, it makes debugging useless, as we don't want to skip the instruction
451  //at the address where we have put the breakpoint.
452  const char *pRegName = m_b64Bit ? "rip" : "eip";
453  WriteRegister(pRegName, it->second);
454  }
455 }
456 
457 bool VBoxGDB::VBoxClient::ReadRegister( const char *pName, ULONGLONG *pValue )
458 {
459  std::vector<DynamicStringA> result = ExecuteCommand(DynamicStringA::sFormat("r %s", pName).c_str());
460  for (size_t i = 0; i < result.size(); i++)
461  {
462  TempStringA regName(ConstStringA(""));
463  TempStringA regVal(ConstStringA(""));
464  size_t initial = 0;
465  if (!FindNextRegVal(result[i], initial, regName, regVal))
466  continue;
467 
468  *pValue = GDBServerFoundation::HexHelpers::ParseHexString<ULONGLONG>(regVal);
469  return true;
470  }
471  return false;
472 
473 }
474 
475 ULONGLONG VBoxGDB::VBoxClient::GetCurrentPC()
476 {
477  const char *pRegName = m_b64Bit ? "rip" : "eip";
478  ULONGLONG val;
479  if (!ReadRegister(pRegName, &val))
480  return 0;
481  return val;
482 
483 }