1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//*****************************************************************************
5// File: breakpoint.cpp
6//
7
8//
9//*****************************************************************************
10#include "stdafx.h"
11
12/* ------------------------------------------------------------------------- *
13 * Breakpoint class
14 * ------------------------------------------------------------------------- */
15
16CordbBreakpoint::CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType)
17 : CordbBase(pProcess, 0, enumCordbBreakpoint),
18 m_active(false), m_pAppDomain(NULL), m_type(bpType)
19{
20}
21
22// Neutered by CordbAppDomain
23void CordbBreakpoint::Neuter()
24{
25 m_pAppDomain = NULL; // clear ref
26 CordbBase::Neuter();
27}
28
29HRESULT CordbBreakpoint::QueryInterface(REFIID id, void **pInterface)
30{
31 if (id == IID_ICorDebugBreakpoint)
32 {
33 *pInterface = static_cast<ICorDebugBreakpoint*>(this);
34 }
35 else if (id == IID_IUnknown)
36 {
37 *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugBreakpoint*>(this));
38 }
39 else
40 {
41 return E_NOINTERFACE;
42 }
43
44 ExternalAddRef();
45 return S_OK;
46}
47
48HRESULT CordbBreakpoint::BaseIsActive(BOOL *pbActive)
49{
50 *pbActive = m_active ? TRUE : FALSE;
51
52 return S_OK;
53}
54
55/* ------------------------------------------------------------------------- *
56 * Function Breakpoint class
57 * ------------------------------------------------------------------------- */
58
59CordbFunctionBreakpoint::CordbFunctionBreakpoint(CordbCode *code,
60 SIZE_T offset,
61 BOOL offsetIsIl)
62 : CordbBreakpoint(code->GetProcess(), CBT_FUNCTION),
63 m_code(code), m_offset(offset),
64 m_offsetIsIl(offsetIsIl)
65{
66 // Remember the app domain we came from so that breakpoints can be
67 // deactivated from within the ExitAppdomain callback.
68 m_pAppDomain = m_code->GetAppDomain();
69 _ASSERTE(m_pAppDomain != NULL);
70}
71
72CordbFunctionBreakpoint::~CordbFunctionBreakpoint()
73{
74 // @todo- eventually get CordbFunctionBreakpoint rooted and enable this.
75 //_ASSERTE(this->IsNeutered());
76 //_ASSERTE(m_code == NULL);
77}
78
79void CordbFunctionBreakpoint::Neuter()
80{
81 Disconnect();
82 CordbBreakpoint::Neuter();
83}
84
85HRESULT CordbFunctionBreakpoint::QueryInterface(REFIID id, void **pInterface)
86{
87 if (id == IID_ICorDebugFunctionBreakpoint)
88 {
89 *pInterface = static_cast<ICorDebugFunctionBreakpoint*>(this);
90 }
91 else
92 {
93 // Not looking for a function breakpoint? See if the base class handles
94 // this interface. (issue 143976)
95 return CordbBreakpoint::QueryInterface(id, pInterface);
96 }
97
98 ExternalAddRef();
99 return S_OK;
100}
101
102HRESULT CordbFunctionBreakpoint::GetFunction(ICorDebugFunction **ppFunction)
103{
104 PUBLIC_API_ENTRY(this);
105 FAIL_IF_NEUTERED(this);
106 VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
107
108 if (m_code == NULL)
109 {
110 return CORDBG_E_PROCESS_TERMINATED;
111 }
112 if (m_code->IsNeutered())
113 {
114 return CORDBG_E_CODE_NOT_AVAILABLE;
115 }
116
117 *ppFunction = static_cast<ICorDebugFunction *> (m_code->GetFunction());
118 (*ppFunction)->AddRef();
119
120 return S_OK;
121}
122
123// m_id is actually a LSPTR_BREAKPOINT. Get it as a type-safe member.
124LSPTR_BREAKPOINT CordbFunctionBreakpoint::GetLsPtrBP()
125{
126 LSPTR_BREAKPOINT p;
127 p.Set((void*) m_id);
128 return p;
129}
130
131HRESULT CordbFunctionBreakpoint::GetOffset(ULONG32 *pnOffset)
132{
133 //REVISIT_TODO: is this casting correct for ia64?
134 PUBLIC_API_ENTRY(this);
135 FAIL_IF_NEUTERED(this);
136 VALIDATE_POINTER_TO_OBJECT(pnOffset, SIZE_T *);
137
138 *pnOffset = (ULONG32)m_offset;
139
140 return S_OK;
141}
142
143//---------------------------------------------------------------------------------------
144//
145// Activates or removes a breakpoint
146//
147// Arguments:
148// fActivate - TRUE if to activate the breakpoint, else FALSE.
149//
150// Return Value:
151// S_OK if successful, else a specific error code detailing the type of failure.
152//
153//---------------------------------------------------------------------------------------
154HRESULT CordbFunctionBreakpoint::Activate(BOOL fActivate)
155{
156 PUBLIC_REENTRANT_API_ENTRY(this);
157 OK_IF_NEUTERED(this); // we'll check again later
158
159 if (fActivate == (m_active == true) )
160 {
161 return S_OK;
162 }
163
164 // For backwards compat w/ everett, we let the other error codes
165 // take precedence over neutering error codes.
166 if ((m_code == NULL) || this->IsNeutered())
167 {
168 return CORDBG_E_PROCESS_TERMINATED;
169 }
170
171 HRESULT hr;
172 ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
173
174 // For legacy, check this error condition. We must do this under the stop-go lock to ensure
175 // that the m_code object was not deleted out from underneath us.
176 //
177 // 6/23/09 - This isn't just for legacy anymore, collectible types should be able to hit this
178 // by unloading the module containing the code this breakpoint is bound to.
179 if (m_code->IsNeutered())
180 {
181 return CORDBG_E_CODE_NOT_AVAILABLE;
182 }
183
184
185 //
186 // <REVISIT_TODO>@todo: when we implement module and value breakpoints, then
187 // we'll want to factor some of this code out.</REVISIT_TODO>
188 //
189 CordbProcess * pProcess = GetProcess();
190
191 RSLockHolder lockHolder(pProcess->GetProcessLock());
192 pProcess->ClearPatchTable(); // if we add something, then the right side
193 // view of the patch table is no longer valid
194
195 DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
196
197 CordbAppDomain * pAppDomain = GetAppDomain();
198 _ASSERTE (pAppDomain != NULL);
199
200 if (fActivate)
201 {
202 pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_ADD, true, pAppDomain->GetADToken());
203
204 pEvent->BreakpointData.funcMetadataToken = m_code->GetMetadataToken();
205 pEvent->BreakpointData.vmDomainFile = m_code->GetModule()->GetRuntimeDomainFile();
206 pEvent->BreakpointData.encVersion = m_code->GetVersion();
207
208 BOOL codeIsIL = m_code->IsIL();
209
210 pEvent->BreakpointData.isIL = m_offsetIsIl ? true : false;
211 pEvent->BreakpointData.offset = m_offset;
212 if (codeIsIL)
213 {
214 pEvent->BreakpointData.nativeCodeMethodDescToken = pEvent->BreakpointData.nativeCodeMethodDescToken.NullPtr();
215 }
216 else
217 {
218 pEvent->BreakpointData.nativeCodeMethodDescToken =
219 (m_code.GetValue()->AsNativeCode())->GetVMNativeCodeMethodDescToken().ToLsPtr();
220 }
221
222 // Note: we're sending a two-way event, so it blocks here
223 // until the breakpoint is really added and the reply event is
224 // copied over the event we sent.
225 lockHolder.Release();
226 hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
227 lockHolder.Acquire();
228
229 hr = WORST_HR(hr, pEvent->hr);
230
231 if (FAILED(hr))
232 {
233 return hr;
234 }
235
236
237 m_id = LsPtrToCookie(pEvent->BreakpointData.breakpointToken);
238
239 // If we weren't able to allocate the BP, we should have set the
240 // hr on the left side.
241 _ASSERTE(m_id != 0);
242
243
244 pAppDomain->m_breakpoints.AddBase(this);
245 m_active = true;
246
247 // Continue called automatically by StopContinueHolder
248 }
249 else
250 {
251 _ASSERTE (pAppDomain != NULL);
252
253 if (pProcess->IsSafeToSendEvents())
254 {
255 pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_REMOVE, false, pAppDomain->GetADToken());
256
257 pEvent->BreakpointData.breakpointToken = GetLsPtrBP();
258
259 lockHolder.Release();
260 hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
261 lockHolder.Acquire();
262
263 hr = WORST_HR(hr, pEvent->hr);
264 }
265 else
266 {
267 hr = CORDBHRFromProcessState(pProcess, pAppDomain);
268 }
269
270 pAppDomain->m_breakpoints.RemoveBase(LsPtrToCookie(GetLsPtrBP()));
271 m_active = false;
272 }
273
274 return hr;
275}
276
277void CordbFunctionBreakpoint::Disconnect()
278{
279 m_code.Clear();
280}
281
282/* ------------------------------------------------------------------------- *
283 * Stepper class
284 * ------------------------------------------------------------------------- */
285
286CordbStepper::CordbStepper(CordbThread *thread, CordbFrame *frame)
287 : CordbBase(thread->GetProcess(), 0, enumCordbStepper),
288 m_thread(thread), m_frame(frame),
289 m_stepperToken(0), m_active(false),
290 m_rangeIL(TRUE),
291 m_fIsJMCStepper(false),
292 m_rgfMappingStop(STOP_OTHER_UNMAPPED),
293 m_rgfInterceptStop(INTERCEPT_NONE)
294{
295}
296
297HRESULT CordbStepper::QueryInterface(REFIID id, void **pInterface)
298{
299 if (id == IID_ICorDebugStepper)
300 *pInterface = static_cast<ICorDebugStepper *>(this);
301 else if (id == IID_ICorDebugStepper2)
302 *pInterface = static_cast<ICorDebugStepper2 *>(this);
303 else if (id == IID_IUnknown)
304 *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugStepper *>(this));
305 else
306 return E_NOINTERFACE;
307
308 ExternalAddRef();
309 return S_OK;
310}
311
312HRESULT CordbStepper::SetRangeIL(BOOL bIL)
313{
314 PUBLIC_API_ENTRY(this);
315 FAIL_IF_NEUTERED(this);
316 m_rangeIL = (bIL != FALSE);
317
318 return S_OK;
319}
320
321HRESULT CordbStepper::SetJMC(BOOL fIsJMCStepper)
322{
323 PUBLIC_API_ENTRY(this);
324 FAIL_IF_NEUTERED(this);
325 // Can't have JMC and stopping with anything else.
326 if (m_rgfMappingStop & STOP_ALL)
327 return E_INVALIDARG;
328
329 m_fIsJMCStepper = (fIsJMCStepper != FALSE);
330 return S_OK;
331}
332
333HRESULT CordbStepper::IsActive(BOOL *pbActive)
334{
335 PUBLIC_API_ENTRY(this);
336 FAIL_IF_NEUTERED(this);
337 VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
338
339 *pbActive = m_active;
340
341 return S_OK;
342}
343
344// M_id is a ptr to the stepper in the LS process.
345LSPTR_STEPPER CordbStepper::GetLsPtrStepper()
346{
347 LSPTR_STEPPER p;
348 p.Set((void*) m_id);
349 return p;
350}
351
352HRESULT CordbStepper::Deactivate()
353{
354 PUBLIC_REENTRANT_API_ENTRY(this);
355 if (!m_active)
356 return S_OK;
357
358 FAIL_IF_NEUTERED(this);
359
360 if (m_thread == NULL)
361 return CORDBG_E_PROCESS_TERMINATED;
362
363 HRESULT hr;
364 CordbProcess *process = GetProcess();
365 ATT_ALLOW_LIVE_DO_STOPGO(process);
366
367 process->Lock();
368
369 if (!m_active) // another thread may be deactivating (e.g. step complete event)
370 {
371 process->Unlock();
372 return S_OK;
373 }
374
375 CordbAppDomain *pAppDomain = GetAppDomain();
376 _ASSERTE (pAppDomain != NULL);
377
378 DebuggerIPCEvent event;
379 process->InitIPCEvent(&event,
380 DB_IPCE_STEP_CANCEL,
381 false,
382 pAppDomain->GetADToken());
383
384 event.StepData.stepperToken = GetLsPtrStepper();
385
386 process->Unlock();
387 hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
388 hr = WORST_HR(hr, event.hr);
389 process->Lock();
390
391
392 process->m_steppers.RemoveBase((ULONG_PTR)m_id);
393 m_active = false;
394
395 process->Unlock();
396
397 return hr;
398}
399
400HRESULT CordbStepper::SetInterceptMask(CorDebugIntercept mask)
401{
402 PUBLIC_API_ENTRY(this);
403 FAIL_IF_NEUTERED(this);
404 m_rgfInterceptStop = mask;
405 return S_OK;
406}
407
408HRESULT CordbStepper::SetUnmappedStopMask(CorDebugUnmappedStop mask)
409{
410 PUBLIC_API_ENTRY(this);
411 FAIL_IF_NEUTERED(this);
412
413 // You must be Win32 attached to stop in unmanaged code.
414 if ((mask & STOP_UNMANAGED) && !GetProcess()->IsInteropDebugging())
415 return E_INVALIDARG;
416
417 // Limitations on JMC Stepping - if JMC stepping is active,
418 // all other stop masks must be disabled.
419 // The jit can't place JMC probes before the prolog, so if we're
420 // we're JMC stepping, we'll stop after the prolog.
421 // The implementation for JMC stepping also doesn't let us stop in
422 // unmanaged code. (because there are no probes there).
423 // So enforce those implementation limitations here.
424 if (m_fIsJMCStepper)
425 {
426 if (mask & STOP_ALL)
427 return E_INVALIDARG;
428 }
429
430 // @todo- Ensure that we only set valid bits.
431
432
433 m_rgfMappingStop = mask;
434 return S_OK;
435}
436
437HRESULT CordbStepper::Step(BOOL bStepIn)
438{
439 PUBLIC_API_ENTRY(this);
440 FAIL_IF_NEUTERED(this);
441 ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
442
443 if (m_thread == NULL)
444 return CORDBG_E_PROCESS_TERMINATED;
445
446 return StepRange(bStepIn, NULL, 0);
447}
448
449//---------------------------------------------------------------------------------------
450//
451// Ships off a step-range command to the left-side. On the next continue the LS will
452// step across one range at a time.
453//
454// Arguments:
455// fStepIn - TRUE if this stepper should execute a step-in, else FALSE
456// rgRanges - Array of ranges that define a single step.
457// cRanges - Count of number of elements in rgRanges.
458//
459// Returns:
460// S_OK if the stepper is successfully set-up, else an appropriate error code.
461//
462HRESULT CordbStepper::StepRange(BOOL fStepIn,
463 COR_DEBUG_STEP_RANGE rgRanges[],
464 ULONG32 cRanges)
465{
466 PUBLIC_REENTRANT_API_ENTRY(this);
467 FAIL_IF_NEUTERED(this);
468 VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(rgRanges, COR_DEBUG_STEP_RANGE, cRanges, true, true);
469
470 ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
471
472 if (m_thread == NULL)
473 {
474 return CORDBG_E_PROCESS_TERMINATED;
475 }
476
477 HRESULT hr = S_OK;
478
479 if (m_active)
480 {
481 //
482 // Deactivate the current stepping.
483 // or return an error???
484 //
485 hr = Deactivate();
486
487 if (FAILED(hr))
488 {
489 return hr;
490 }
491 }
492
493 // Validate step-ranges. Ranges are exclusive, so end offset
494 // should always be greater than start offset.
495 // Ranges don't have to be sorted.
496 // Zero ranges is ok; though they ought to just call Step() in that case.
497 for (ULONG32 i = 0; i < cRanges; i++)
498 {
499 if (rgRanges[i].startOffset >= rgRanges[i].endOffset)
500 {
501 STRESS_LOG2(LF_CORDB, LL_INFO10, "Illegal step range. 0x%x-0x%x\n", rgRanges[i].startOffset, rgRanges[i].endOffset);
502 return ErrWrapper(E_INVALIDARG);
503 }
504 }
505
506 CordbProcess * pProcess = GetProcess();
507
508 //
509 // Build step event
510 //
511
512 DebuggerIPCEvent * pEvent = reinterpret_cast<DebuggerIPCEvent *>(_alloca(CorDBIPC_BUFFER_SIZE));
513
514 pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP, true, GetAppDomain()->GetADToken());
515
516 pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
517 pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
518 pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
519 pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
520
521
522 if (m_frame == NULL)
523 {
524 pEvent->StepData.frameToken = LEAF_MOST_FRAME;
525 }
526 else
527 {
528 pEvent->StepData.frameToken = m_frame->GetFramePointer();
529 }
530
531 pEvent->StepData.stepIn = (fStepIn != 0);
532 pEvent->StepData.totalRangeCount = cRanges;
533 pEvent->StepData.rangeIL = m_rangeIL;
534
535 //
536 // Send ranges. We may have to send > 1 message.
537 //
538
539 COR_DEBUG_STEP_RANGE * pRangeStart = &(pEvent->StepData.range);
540 COR_DEBUG_STEP_RANGE * pRangeEnd = (reinterpret_cast<COR_DEBUG_STEP_RANGE *> (((BYTE *)pEvent) + CorDBIPC_BUFFER_SIZE)) - 1;
541
542 int cRangesToGo = cRanges;
543
544 if (cRangesToGo > 0)
545 {
546 while (cRangesToGo > 0)
547 {
548 //
549 // Find the number of ranges we can copy this time thru the loop
550 //
551 int cRangesToCopy;
552
553 if (cRangesToGo < (pRangeEnd - pRangeStart))
554 {
555 cRangesToCopy = cRangesToGo;
556 }
557 else
558 {
559 cRangesToCopy = (unsigned int)(pRangeEnd - pRangeStart);
560 }
561
562 //
563 // Copy the ranges into the IPC block now, 1-by-1
564 //
565 int cRangesCopied = 0;
566
567 while (cRangesCopied != cRangesToCopy)
568 {
569 pRangeStart[cRangesCopied] = rgRanges[cRanges - cRangesToGo + cRangesCopied];
570 cRangesCopied++;
571 }
572
573 pEvent->StepData.rangeCount = cRangesCopied;
574
575 cRangesToGo -= cRangesCopied;
576
577 //
578 // Send step event (two-way event here...)
579 //
580
581 hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
582
583 hr = WORST_HR(hr, pEvent->hr);
584
585 if (FAILED(hr))
586 {
587 return hr;
588 }
589 }
590 }
591 else
592 {
593 //
594 // Send step event without any ranges (two-way event here...)
595 //
596
597 hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
598
599 hr = WORST_HR(hr, pEvent->hr);
600
601 if (FAILED(hr))
602 {
603 return hr;
604 }
605 }
606
607 m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
608
609 LOG((LF_CORDB,LL_INFO10000, "CS::SR: m_id:0x%x | 0x%x \n",
610 m_id,
611 LsPtrToCookie(pEvent->StepData.stepperToken)));
612
613#ifdef _DEBUG
614 CordbAppDomain *pAppDomain = GetAppDomain();
615#endif
616 _ASSERTE (pAppDomain != NULL);
617
618 pProcess->Lock();
619
620 pProcess->m_steppers.AddBase(this);
621 m_active = true;
622
623 pProcess->Unlock();
624
625 return hr;
626}
627
628//---------------------------------------------------------------------------------------
629//
630// Ships off a step-out command to the left-side. On the next continue the LS will
631// execute a step-out
632//
633// Returns:
634// S_OK if the stepper is successfully set-up, else an appropriate error code.
635//
636HRESULT CordbStepper::StepOut()
637{
638 PUBLIC_API_ENTRY(this);
639 FAIL_IF_NEUTERED(this);
640 ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
641
642 if (m_thread == NULL)
643 {
644 return CORDBG_E_PROCESS_TERMINATED;
645 }
646
647 HRESULT hr;
648
649 if (m_active)
650 {
651 //
652 // Deactivate the current stepping.
653 // or return an error???
654 //
655
656 hr = Deactivate();
657
658 if (FAILED(hr))
659 {
660 return hr;
661 }
662 }
663
664 CordbProcess * pProcess = GetProcess();
665
666 // We don't do native step-out.
667 if (pProcess->SupportsVersion(ver_ICorDebugProcess2))
668 {
669 if ((m_rgfMappingStop & STOP_UNMANAGED) != 0)
670 {
671 return ErrWrapper(CORDBG_E_CANT_INTEROP_STEP_OUT);
672 }
673 }
674
675 //
676 // Build step event
677 //
678
679 DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
680
681 pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP_OUT, true, GetAppDomain()->GetADToken());
682
683 pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
684 pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
685 pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
686 pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
687
688 if (m_frame == NULL)
689 {
690 pEvent->StepData.frameToken = LEAF_MOST_FRAME;
691 }
692 else
693 {
694 pEvent->StepData.frameToken = m_frame->GetFramePointer();
695 }
696
697 pEvent->StepData.totalRangeCount = 0;
698
699 // Note: two-way event here...
700 hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
701
702 hr = WORST_HR(hr, pEvent->hr);
703
704 if (FAILED(hr))
705 {
706 return hr;
707 }
708
709 m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
710
711#ifdef _DEBUG
712 CordbAppDomain * pAppDomain = GetAppDomain();
713#endif
714 _ASSERTE (pAppDomain != NULL);
715
716 pProcess->Lock();
717
718 pProcess->m_steppers.AddBase(this);
719 m_active = true;
720
721 pProcess->Unlock();
722
723 return S_OK;
724}
725