PageRenderTime 37ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/vm/debugger_hook.cpp

https://bitbucket.org/gnanakeethan/hiphop-php
C++ | 356 lines | 273 code | 34 blank | 49 comment | 55 complexity | 827b673d6332398f953980bb9aa3157e MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/runtime/vm/debugger_hook.h"
  17. #include "hphp/runtime/vm/translator/translator.h"
  18. #include "hphp/runtime/eval/debugger/break_point.h"
  19. #include "hphp/runtime/eval/debugger/debugger.h"
  20. #include "hphp/runtime/eval/debugger/debugger_proxy.h"
  21. #include "hphp/runtime/eval/runtime/file_repository.h"
  22. #include "hphp/util/logger.h"
  23. #include "hphp/util/util.h"
  24. namespace HPHP {
  25. //////////////////////////////////////////////////////////////////////////
  26. static const Trace::Module TRACEMOD = Trace::bcinterp;
  27. static inline Transl::Translator* transl() {
  28. return Transl::Translator::Get();
  29. }
  30. // Hook called from the bytecode interpreter before every opcode executed while
  31. // a debugger is attached. The debugger may choose to hold the thread below
  32. // here and execute any number of commands from the client. Return from here
  33. // lets the opcode execute.
  34. void phpDebuggerOpcodeHook(const uchar* pc) {
  35. TRACE(5, "in phpDebuggerOpcodeHook()\n");
  36. // Short-circuit when we're doing things like evaling PHP for print command,
  37. // or conditional breakpoints.
  38. if (UNLIKELY(g_vmContext->m_dbgNoBreak)) {
  39. TRACE(5, "NoBreak flag is on\n");
  40. return;
  41. }
  42. // Short-circuit for cases where we're executing a line of code that we know
  43. // we don't need an interrupt for, e.g., stepping over a line of code.
  44. if (UNLIKELY(g_vmContext->m_lastLocFilter != nullptr) &&
  45. g_vmContext->m_lastLocFilter->checkPC(pc)) {
  46. TRACE_RB(5, "same location as last interrupt\n");
  47. return;
  48. }
  49. // Are we hitting a breakpoint?
  50. if (LIKELY(g_vmContext->m_breakPointFilter == nullptr ||
  51. !g_vmContext->m_breakPointFilter->checkPC(pc))) {
  52. TRACE(5, "not in the PC range for any breakpoints\n");
  53. if (LIKELY(!DEBUGGER_FORCE_INTR)) {
  54. return;
  55. }
  56. TRACE_RB(5, "DEBUGGER_FORCE_INTR\n");
  57. }
  58. Eval::Debugger::InterruptVMHook();
  59. TRACE(5, "out phpDebuggerOpcodeHook()\n");
  60. }
  61. // Hook called from iopThrow to signal that we are about to throw an exception.
  62. // NB: this does not hook any portion of exception unwind.
  63. void phpDebuggerExceptionHook(ObjectData* e) {
  64. TRACE(5, "in phpDebuggerExceptionHook()\n");
  65. if (UNLIKELY(g_vmContext->m_dbgNoBreak)) {
  66. TRACE(5, "NoBreak flag is on\n");
  67. return;
  68. }
  69. Eval::Debugger::InterruptVMHook(Eval::ExceptionThrown, e);
  70. TRACE(5, "out phpDebuggerExceptionHook()\n");
  71. }
  72. bool isDebuggerAttachedProcess() {
  73. return Eval::Debugger::CountConnectedProxy() > 0;
  74. }
  75. // Ensure we interpret all code at the given offsets. This sets up a guard for
  76. // each piece of translated code to ensure we punt to the interpreter when the
  77. // debugger is attached.
  78. static void blacklistRangesInJit(const Unit* unit,
  79. const OffsetRangeVec& offsets) {
  80. for (OffsetRangeVec::const_iterator it = offsets.begin();
  81. it != offsets.end(); ++it) {
  82. for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past);
  83. pc += instrLen((Opcode*)pc)) {
  84. transl()->addDbgBLPC(pc);
  85. }
  86. }
  87. if (!transl()->addDbgGuards(unit)) {
  88. Logger::Warning("Failed to set breakpoints in Jitted code");
  89. }
  90. // In this case, we may be setting a breakpoint in a tracelet which could
  91. // already be jitted, and present on the stack. Make sure we don't return
  92. // to it so we have a chance to honor breakpoints.
  93. g_vmContext->preventReturnsToTC();
  94. }
  95. // Ensure we interpret an entire function when the debugger is attached.
  96. static void blacklistFuncInJit(const Func* f) {
  97. Unit* unit = f->unit();
  98. OffsetRangeVec ranges;
  99. ranges.push_back(OffsetRange(f->base(), f->past()));
  100. blacklistRangesInJit(unit, ranges);
  101. }
  102. static PCFilter *getBreakPointFilter() {
  103. if (!g_vmContext->m_breakPointFilter) {
  104. g_vmContext->m_breakPointFilter = new PCFilter();
  105. }
  106. return g_vmContext->m_breakPointFilter;
  107. }
  108. static void addBreakPointsInFile(Eval::DebuggerProxy* proxy,
  109. Eval::PhpFile* efile) {
  110. Eval::BreakPointInfoPtrVec bps;
  111. proxy->getBreakPoints(bps);
  112. for(unsigned int i = 0; i < bps.size(); i++) {
  113. Eval::BreakPointInfoPtr bp = bps[i];
  114. if (bp->m_line1 == 0 || bp->m_file.empty()) {
  115. // invalid breakpoint for file:line
  116. continue;
  117. }
  118. if (!Eval::BreakPointInfo::MatchFile(bp->m_file, efile->getFileName(),
  119. efile->getRelPath())) {
  120. continue;
  121. }
  122. Unit* unit = efile->unit();
  123. OffsetRangeVec offsets;
  124. if (!unit->getOffsetRanges(bp->m_line1, offsets)) {
  125. continue;
  126. }
  127. if (debug && Trace::moduleEnabled(Trace::bcinterp, 5)) {
  128. for (OffsetRangeVec::const_iterator it = offsets.begin();
  129. it != offsets.end(); ++it) {
  130. Trace::trace("file:line break %s:%d : unit %p offset [%d, %d)\n",
  131. efile->getFileName().c_str(), bp->m_line1, unit,
  132. it->m_base, it->m_past);
  133. }
  134. }
  135. getBreakPointFilter()->addRanges(unit, offsets);
  136. if (RuntimeOption::EvalJit) {
  137. blacklistRangesInJit(unit, offsets);
  138. }
  139. }
  140. }
  141. static void addBreakPointFuncEntry(const Func* f) {
  142. PC pc = f->unit()->at(f->base());
  143. TRACE(5, "func() break %s : unit %p offset %d)\n",
  144. f->fullName()->data(), f->unit(), f->base());
  145. getBreakPointFilter()->addPC(pc);
  146. if (RuntimeOption::EvalJit) {
  147. if (transl()->addDbgBLPC(pc)) {
  148. // if a new entry is added in blacklist
  149. if (!transl()->addDbgGuard(f, f->base())) {
  150. Logger::Warning("Failed to set breakpoints in Jitted code");
  151. }
  152. }
  153. }
  154. }
  155. static void addBreakPointsClass(Eval::DebuggerProxy* proxy,
  156. const Class* cls) {
  157. size_t numFuncs = cls->numMethods();
  158. Func* const* funcs = cls->methods();
  159. for (size_t i = 0; i < numFuncs; ++i) {
  160. if (proxy->couldBreakEnterFunc(funcs[i]->fullName())) {
  161. addBreakPointFuncEntry(funcs[i]);
  162. }
  163. }
  164. }
  165. void phpAddBreakPoint(const Unit* unit, Offset offset) {
  166. PC pc = unit->at(offset);
  167. getBreakPointFilter()->addPC(pc);
  168. if (RuntimeOption::EvalJit) {
  169. if (transl()->addDbgBLPC(pc)) {
  170. // if a new entry is added in blacklist
  171. if (!transl()->addDbgGuards(unit)) {
  172. Logger::Warning("Failed to set breakpoints in Jitted code");
  173. }
  174. // In this case, we may be setting a breakpoint in a tracelet which could
  175. // already be jitted, and present on the stack. Make sure we don't return
  176. // to it so we have a chance to honor breakpoints.
  177. g_vmContext->preventReturnsToTC();
  178. }
  179. }
  180. }
  181. void phpRemoveBreakPoint(const Unit* unit, Offset offset) {
  182. if (g_vmContext->m_breakPointFilter) {
  183. PC pc = unit->at(offset);
  184. g_vmContext->m_breakPointFilter->removePC(pc);
  185. }
  186. }
  187. void phpDebuggerEvalHook(const Func* f) {
  188. if (RuntimeOption::EvalJit) {
  189. blacklistFuncInJit(f);
  190. }
  191. }
  192. // Hook called by the VM when a file is loaded. Gives the debugger a chance
  193. // to apply any pending breakpoints that might be in the file.
  194. void phpDebuggerFileLoadHook(Eval::PhpFile* efile) {
  195. Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy();
  196. if (!proxy) {
  197. return;
  198. }
  199. addBreakPointsInFile(proxy.get(), efile);
  200. }
  201. void phpDebuggerDefClassHook(const Class* cls) {
  202. Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy();
  203. if (!proxy) {
  204. return;
  205. }
  206. addBreakPointsClass(proxy.get(), cls);
  207. }
  208. void phpDebuggerDefFuncHook(const Func* func) {
  209. Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy();
  210. if (proxy && proxy->couldBreakEnterFunc(func->fullName())) {
  211. addBreakPointFuncEntry(func);
  212. }
  213. }
  214. // Helper which will look at every loaded file and attempt to see if any
  215. // existing file:line breakpoints should be set.
  216. void phpSetBreakPointsInAllFiles(Eval::DebuggerProxy* proxy) {
  217. for (EvaledFilesMap::const_iterator it =
  218. g_vmContext->m_evaledFiles.begin();
  219. it != g_vmContext->m_evaledFiles.end(); ++it) {
  220. addBreakPointsInFile(proxy, it->second);
  221. }
  222. std::vector<const StringData*> clsNames;
  223. proxy->getBreakClsMethods(clsNames);
  224. for (unsigned int i = 0; i < clsNames.size(); i++) {
  225. Class* cls = Unit::lookupClass(clsNames[i]);
  226. if (cls) {
  227. addBreakPointsClass(proxy, cls);
  228. }
  229. }
  230. std::vector<const StringData*> funcFullNames;
  231. proxy->getBreakFuncs(funcFullNames);
  232. for (unsigned int i = 0; i < funcFullNames.size(); i++) {
  233. // This list contains class method as well but they shouldn't hit anything
  234. Func* f = Unit::lookupFunc(funcFullNames[i]);
  235. if (f) {
  236. addBreakPointFuncEntry(f);
  237. }
  238. }
  239. }
  240. //////////////////////////////////////////////////////////////////////////
  241. struct PCFilter::PtrMapNode {
  242. void **m_entries;
  243. void clearImpl(unsigned short bits);
  244. };
  245. void PCFilter::PtrMapNode::clearImpl(unsigned short bits) {
  246. // clear all the sub levels and mark all slots NULL
  247. if (bits <= PTRMAP_LEVEL_BITS) {
  248. assert(bits == PTRMAP_LEVEL_BITS);
  249. // On bottom level, pointers are not PtrMapNode*
  250. memset(m_entries, 0, sizeof(void*) * PTRMAP_LEVEL_ENTRIES);
  251. return;
  252. }
  253. for (int i = 0; i < PTRMAP_LEVEL_ENTRIES; i++) {
  254. if (m_entries[i]) {
  255. ((PCFilter::PtrMapNode*)m_entries[i])->clearImpl(bits -
  256. PTRMAP_LEVEL_BITS);
  257. free(((PCFilter::PtrMapNode*)m_entries[i])->m_entries);
  258. free(m_entries[i]);
  259. m_entries[i] = nullptr;
  260. }
  261. }
  262. }
  263. PCFilter::PtrMapNode* PCFilter::PtrMap::MakeNode() {
  264. PtrMapNode* node = (PtrMapNode*)malloc(sizeof(PtrMapNode));
  265. node->m_entries =
  266. (void**)calloc(1, PTRMAP_LEVEL_ENTRIES * sizeof(void*));
  267. return node;
  268. }
  269. PCFilter::PtrMap::~PtrMap() {
  270. clear();
  271. free(m_root->m_entries);
  272. free(m_root);
  273. }
  274. void* PCFilter::PtrMap::getPointer(void* ptr) {
  275. PtrMapNode* current = m_root;
  276. unsigned short cursor = PTRMAP_PTR_SIZE;
  277. while (current && cursor) {
  278. cursor -= PTRMAP_LEVEL_BITS;
  279. unsigned long index = ((PTRMAP_LEVEL_MASK << cursor) & (unsigned long)ptr)
  280. >> cursor;
  281. assert(index < PTRMAP_LEVEL_ENTRIES);
  282. current = (PtrMapNode*)(current->m_entries[index]);
  283. }
  284. return (void*)current;
  285. }
  286. void PCFilter::PtrMap::setPointer(void* ptr, void* val) {
  287. PtrMapNode* current = m_root;
  288. unsigned short cursor = PTRMAP_PTR_SIZE;
  289. while (true) {
  290. cursor -= PTRMAP_LEVEL_BITS;
  291. unsigned long index = ((PTRMAP_LEVEL_MASK << cursor) & (unsigned long)ptr)
  292. >> cursor;
  293. assert(index < PTRMAP_LEVEL_ENTRIES);
  294. if (!cursor) {
  295. current->m_entries[index] = val;
  296. break;
  297. }
  298. if (!current->m_entries[index]) {
  299. current->m_entries[index] = (void*) MakeNode();
  300. }
  301. current = (PtrMapNode*)(current->m_entries[index]);
  302. }
  303. }
  304. void PCFilter::PtrMap::clear() {
  305. m_root->clearImpl(PTRMAP_PTR_SIZE);
  306. }
  307. int PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) {
  308. int counter = 0;
  309. for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) {
  310. for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past);
  311. pc += instrLen((Opcode*)pc)) {
  312. addPC(pc);
  313. counter++;
  314. }
  315. }
  316. return counter;
  317. }
  318. void PCFilter::removeOffset(const Unit* unit, Offset offset) {
  319. removePC(unit->at(offset));
  320. }
  321. //////////////////////////////////////////////////////////////////////////
  322. }