Отладчик Visual Studio не выполняет команду «call»

#c# #assembly #visual-studio-debugging

#c# #сборка #visual-studio-debugging

Вопрос:

Я исследовал некоторые детали ассемблерного кода, который выполняется для некоторых типичных конструкций C #. При отладке простого кода C # я слежу за его выполнением в окне дизассемблирования Visual Studio (это релизная сборка приложения с полной отладочной информацией).

У нас есть следующий фрагмент кода:

 return Interlocked.Increment(ref _boxedInt);

00007FFD6CFB5522  sub         esp,30h  
00007FFD6CFB5525  lea         rbp,[rsp 30h]  
00007FFD6CFB552A  mov         qword ptr [rbp 10h],rcx  
00007FFD6CFB552E  cmp         dword ptr [7FFD6C166370h],0  
00007FFD6CFB5535  je          00007FFD6CFB553C  
00007FFD6CFB5537  call        00007FFDCBCFFCC0  
00007FFD6CFB553C  mov         rcx,qword ptr [rbp 10h]  
00007FFD6CFB5540  cmp         dword ptr [rcx],ecx  
00007FFD6CFB5542  mov         rcx,qword ptr [rbp 10h]  
00007FFD6CFB5546  add         rcx,8  
00007FFD6CFB554A  call        00007FFDCA6624B0  
00007FFD6CFB554F  mov         dword ptr [rbp-4],eax  
00007FFD6CFB5552  mov         eax,dword ptr [rbp-4]  
00007FFD6CFB5555  lea         rsp,[rbp]  
00007FFD6CFB5559  pop         rbp  
00007FFD6CFB555A  ret  
  

Существует проблема с инструкцией call по адресу 00007FFD6CFB554A (которая на самом деле является вызовом Interlocked.Увеличение), потому что отладчик Visual Studio просто выполняет вызов и не переходит к выполнению подпрограммы.

Я намеревался посмотреть, какой код выполняется при блокировке.Выполняется увеличение.

  1. Почему отладчик не выполняет выполнение в вызываемой подпрограмме?

  2. Как заставить его выполнить этот вызов (для проектов C # уже включена смешанная отладка)?

Комментарии:

1. learn.microsoft.com/en-us/visualstudio/debugger/…

2. Это неуправляемый код, встроенный в среду CLR, вы не можете ни выполнить его, ни просмотреть с помощью управляемого отладчика. Требуется хитрость, чтобы обмануть отладчик. Включите неуправляемую отладку и вернитесь к точке останова. Обратите внимание на этот адрес вызова. Используйте Debug > Windows > Call Stack и дважды щелкните фрейм ntdll внизу. Это переключает механизм отладчика на неуправляемый. Теперь вы можете ввести адрес в адресное поле и просмотреть машинный код.

3. Обратите внимание, что обычно это имеет смысл делать только с оптимизированной сборкой выпуска. Инструменты> Параметры> Отладка> Общие, флажок «Подавить оптимизацию JIT» снят. Заблокировано. Increment() является встроенным, обычным генератором кода для этого встроенного, а не вызова CLR. Должна отображаться инструкция LOCK INC для переменной типа int для x86, LOCK ADD для x64.

Ответ №1:

Спасибо, Ганс, это сработало … каким-то образом 😉

Без оптимизации JIT это выглядит как:

 00007FFDCA6624B0  nop         dword ptr [rax rax]  
00007FFDCA6624B5  mov         eax,1  
00007FFDCA6624BA  lock xadd   dword ptr [rcx],eax  
00007FFDCA6624BE  inc         eax  
00007FFDCA6624C0  ret  
  

С оптимизацией JIT все намного сложнее. Это всего лишь фрагмент кода, структуру которого трудно охватить, но она есть:

 00007FFD6CD7219F  lea         rax,[rsi 8]  
00007FFD6CD721A3  mov         edx,1  
00007FFD6CD721A8  lock xadd   dword ptr [rax],edx  
00007FFD6CD721AC  lea         eax,[rdx 1]  
  

Похоже, что он возвращает увеличенное значение в eax.

Хотя мне удалось достичь своей цели, у меня возникли некоторые трудности.

  1. Когда отключили «Подавлять оптимизацию JIT при загрузке модуля» и установили точку останова в коде, я не мог отслеживать выполнение в окне сборки. Процесс был завершен (нарушение доступа) при выполнении команды первого вызова. Мне пришлось применить другой подход и переключиться на Debugger.Вызов Break() непосредственно перед блокировкой.Увеличьте код C # и присоедините отладчик, заставляя его обрабатывать мой процесс .NET как собственный процесс:

    • запустите мое приложение без отладки
    • подключите отладчик, как если бы мое приложение было встроенным
    • триггер заблокирован.Увеличьте выполнение (с остановом отладчика непосредственно перед ним).

И я смог отследить то, что искал. Но почему все это завершилось сбоем, если мое приложение было запущено непосредственно с помощью отладки в VS? Я полагаю, что отладчик не был подключен к приложению, как если бы оно было встроенным. Но почему это должно иметь значение, если все, о чем мы заботимся, — это поток инструкций в окне сборки?

  1. Учитывая, что мы сохраняем включенным «Подавление JIT-оптимизации при загрузке модуля», почему отладчик не выполняет вызов и не раскрывает код внутри Interlocked.Процедура увеличения? Еще раз — это всего лишь инструкции процессора. Нет управляемых и встроенных инструкций, верно?

  2. Это было упомянуто в комментарии, который заблокирован.Приращение — это неуправляемый код. Каким образом это неуправляемо, поскольку все сводится к нескольким инструкциям процессора? Что делает его неуправляемым и почему? Это не системный вызов или что-либо еще, что зависит от неуправляемых ресурсов. Все, на что он ссылается и что использует, на самом деле управляется. Тогда почему?

Комментарии:

1. Привет, мы рады слышать, что ваша проблема решена, спасибо, что поделились. Пожалуйста, отметьте свой ответ как answer, и это поможет другим членам сообщества упростить поиск этой полезной информации, заранее спасибо.